@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,240 @@
1
+ import { z } from "zod";
2
+ import { createProjectBankManifest, } from "../../core/bank/project.js";
3
+ import { buildCreateBankPrompt } from "../../core/projects/createBankPrompt.js";
4
+ import { finalizeCreateBankExecution, resolveCreateBankFlowContext } from "../../core/projects/createBankFlow.js";
5
+ import { discoverCurrentProjectBank } from "../../core/projects/discoverCurrentProjectBank.js";
6
+ import { buildCreateBankIterationPrompt, buildReadyProjectBankPrompt, } from "../../core/projects/createBankIterationPrompt.js";
7
+ import { getCreateFlowPhase } from "../../core/projects/createFlowPhases.js";
8
+ import { applyCreateBankChanges } from "./createBankApply.js";
9
+ import { buildCreateBankResponseText, getCreateBankApplyBlockedMessage, normalizeApplyDeletions, normalizeApplyWrites, shouldWarnAboutIterationMismatch, } from "./createBankToolRuntime.js";
10
+ import { writeToolAuditEvent } from "./auditUtils.js";
11
+ import { CreateBankArgsSchema, CreateBankInputShape, CreateBankOutputShape, } from "./createBankToolSchemas.js";
12
+ const registerCreateLikeTool = (server, options, { toolName, title, description, }) => {
13
+ server.registerTool(toolName, {
14
+ title,
15
+ description,
16
+ annotations: {
17
+ readOnlyHint: false,
18
+ destructiveHint: false,
19
+ },
20
+ inputSchema: CreateBankInputShape,
21
+ outputSchema: CreateBankOutputShape,
22
+ }, async (args) => {
23
+ const parsedArgs = CreateBankArgsSchema.safeParse(args);
24
+ if (!parsedArgs.success) {
25
+ return {
26
+ isError: true,
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: `Invalid arguments for tool ${toolName}: ${z.prettifyError(parsedArgs.error)}`,
31
+ },
32
+ ],
33
+ };
34
+ }
35
+ const requestedIteration = parsedArgs.data.iteration ?? 0;
36
+ const flowContext = await resolveCreateBankFlowContext({
37
+ repository: options.repository,
38
+ projectPath: parsedArgs.data.projectPath,
39
+ requestedIteration,
40
+ stepCompleted: parsedArgs.data.stepCompleted ?? false,
41
+ hasApply: parsedArgs.data.apply !== undefined,
42
+ stepOutcome: parsedArgs.data.stepOutcome ?? null,
43
+ stepOutcomeNote: parsedArgs.data.stepOutcomeNote ?? null,
44
+ ...(parsedArgs.data.sourceReviewDecision ? { sourceReviewDecision: parsedArgs.data.sourceReviewDecision } : {}),
45
+ ...(parsedArgs.data.referenceProjectIds ? { referenceProjectIds: parsedArgs.data.referenceProjectIds } : {}),
46
+ });
47
+ if (flowContext.unknownReferenceIds.length > 0) {
48
+ return {
49
+ isError: true,
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: `Unknown reference project ids for tool ${toolName}: ${flowContext.unknownReferenceIds.join(", ")}`,
54
+ },
55
+ ],
56
+ };
57
+ }
58
+ const { identity, projectContext, existingManifest, existingState, selectedReferenceProjects, existingBankUpdatedAt, existingBankUpdatedDaysAgo, effectiveIteration, stepCompletionRequired, sourceStrategyRequired, stepOutcomeRequired, syncRequired, improvementEntryPoint, extendedContext, confirmedSourceStrategies, } = flowContext;
59
+ if (existingState !== null &&
60
+ shouldWarnAboutIterationMismatch(existingState.createIteration, requestedIteration, effectiveIteration, stepCompletionRequired, sourceStrategyRequired, stepOutcomeRequired)) {
61
+ console.warn(`create_bank iteration mismatch for project ${identity.projectId}: stored=${existingState.createIteration}, requested=${requestedIteration}, effective=${effectiveIteration}. Overwriting stored iteration.`);
62
+ }
63
+ const applyBlockedMessage = getCreateBankApplyBlockedMessage({
64
+ hasApply: parsedArgs.data.apply !== undefined,
65
+ syncRequired,
66
+ improvementEntryPoint,
67
+ phase: syncRequired
68
+ ? "sync_required"
69
+ : improvementEntryPoint
70
+ ? "ready_to_improve"
71
+ : getCreateFlowPhase(effectiveIteration),
72
+ hasDiscoveredSources: extendedContext.discoveredSources.length > 0,
73
+ stepCompletionRequired,
74
+ sourceStrategyRequired,
75
+ stepOutcomeRequired,
76
+ });
77
+ if (applyBlockedMessage !== null) {
78
+ return {
79
+ isError: true,
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: applyBlockedMessage,
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ if (existingManifest === null) {
89
+ await options.repository.ensureProjectStructure(identity.projectId);
90
+ await options.repository.writeProjectManifest(identity.projectId, createProjectBankManifest(identity.projectId, identity.projectName, identity.projectPath, projectContext.detectedStacks));
91
+ }
92
+ const projectBankPath = options.repository.paths.projectDirectory(identity.projectId);
93
+ const rulesDirectory = options.repository.paths.projectRulesDirectory(identity.projectId);
94
+ const skillsDirectory = options.repository.paths.projectSkillsDirectory(identity.projectId);
95
+ let currentBankSnapshot = existingManifest === null
96
+ ? {
97
+ ...extendedContext.currentBankSnapshot,
98
+ exists: true,
99
+ }
100
+ : extendedContext.currentBankSnapshot;
101
+ const applyResults = parsedArgs.data.apply
102
+ ? await applyCreateBankChanges({
103
+ repository: options.repository,
104
+ auditLogger: options.auditLogger,
105
+ projectId: identity.projectId,
106
+ projectPath: identity.projectPath,
107
+ sessionRef: parsedArgs.data.sessionRef ?? null,
108
+ writes: normalizeApplyWrites(parsedArgs.data.apply.writes),
109
+ deletions: normalizeApplyDeletions(parsedArgs.data.apply.deletions),
110
+ })
111
+ : {
112
+ writes: [],
113
+ deletions: [],
114
+ };
115
+ if (parsedArgs.data.apply) {
116
+ currentBankSnapshot = await discoverCurrentProjectBank(options.repository, identity.projectId, true);
117
+ }
118
+ const { effectiveIteration: finalEffectiveIteration, phase: finalPhase, stepCompletionRequired: finalStepCompletionRequired, stepOutcomeRequired: finalStepOutcomeRequired, mustContinue: finalMustContinue, nextIteration: finalNextIteration, completedFlowThisCall: finalCompletedFlowThisCall, nextState, } = finalizeCreateBankExecution({
119
+ flowContext,
120
+ requestedIteration,
121
+ stepCompleted: parsedArgs.data.stepCompleted ?? false,
122
+ stepOutcome: parsedArgs.data.stepOutcome ?? null,
123
+ stepOutcomeNote: parsedArgs.data.stepOutcomeNote ?? null,
124
+ applyResults,
125
+ });
126
+ await options.repository.writeProjectState(identity.projectId, nextState);
127
+ const prompt = syncRequired
128
+ ? "Project AI Guidance Bank already exists for this repository and requires synchronization before reuse. Sync only reconciles the existing bank with the current AI Guidance Bank storage version; it does not create or improve project content. Ask the user whether to synchronize it now or postpone it. After that, call `resolve_context` again."
129
+ : improvementEntryPoint
130
+ ? buildReadyProjectBankPrompt({
131
+ updatedAt: existingBankUpdatedAt,
132
+ updatedDaysAgo: existingBankUpdatedDaysAgo,
133
+ })
134
+ : finalMustContinue || finalCompletedFlowThisCall
135
+ ? buildCreateBankIterationPrompt({
136
+ iteration: finalEffectiveIteration,
137
+ projectName: identity.projectName,
138
+ projectPath: identity.projectPath,
139
+ projectBankPath,
140
+ rulesDirectory,
141
+ skillsDirectory,
142
+ detectedStacks: projectContext.detectedStacks,
143
+ selectedReferenceProjects,
144
+ discoveredSources: extendedContext.discoveredSources,
145
+ confirmedSourceStrategies,
146
+ currentBankSnapshot,
147
+ hasExistingProjectBank: existingManifest !== null,
148
+ })
149
+ : "Project AI Guidance Bank already exists for this repository and is ready.";
150
+ const creationPrompt = finalEffectiveIteration === 0
151
+ ? buildCreateBankPrompt({
152
+ projectName: identity.projectName,
153
+ projectPath: identity.projectPath,
154
+ projectBankPath,
155
+ rulesDirectory,
156
+ skillsDirectory,
157
+ detectedStacks: projectContext.detectedStacks,
158
+ selectedReferenceProjects,
159
+ })
160
+ : null;
161
+ const payload = {
162
+ status: existingManifest === null ? "created" : "already_exists",
163
+ syncRequired,
164
+ projectId: identity.projectId,
165
+ projectName: identity.projectName,
166
+ projectPath: identity.projectPath,
167
+ projectBankPath,
168
+ rulesDirectory,
169
+ skillsDirectory,
170
+ detectedStacks: projectContext.detectedStacks,
171
+ phase: finalPhase,
172
+ iteration: finalEffectiveIteration,
173
+ discoveredSources: extendedContext.discoveredSources,
174
+ currentBankSnapshot,
175
+ selectedReferenceProjects,
176
+ creationState: nextState.creationState,
177
+ confirmedSourceStrategies,
178
+ stepCompletionRequired: finalStepCompletionRequired,
179
+ sourceStrategyRequired,
180
+ stepOutcomeRequired: finalStepOutcomeRequired,
181
+ mustContinue: finalMustContinue,
182
+ nextIteration: finalNextIteration,
183
+ existingBankUpdatedAt,
184
+ existingBankUpdatedDaysAgo,
185
+ applyResults,
186
+ prompt,
187
+ creationPrompt,
188
+ text: buildCreateBankResponseText({
189
+ syncRequired,
190
+ applyResults,
191
+ stepCompletionRequired: finalStepCompletionRequired,
192
+ sourceStrategyRequired,
193
+ stepOutcomeRequired: finalStepOutcomeRequired,
194
+ nextIteration: finalNextIteration,
195
+ improvementEntryPoint,
196
+ mustContinue: finalMustContinue,
197
+ completedFlowThisCall: finalCompletedFlowThisCall,
198
+ phase: finalPhase,
199
+ }),
200
+ };
201
+ await writeToolAuditEvent({
202
+ auditLogger: options.auditLogger,
203
+ sessionRef: parsedArgs.data.sessionRef,
204
+ tool: toolName,
205
+ action: "create_flow",
206
+ projectId: identity.projectId,
207
+ projectPath: identity.projectPath,
208
+ details: {
209
+ phase: finalPhase,
210
+ iteration: finalEffectiveIteration,
211
+ creationState: nextState.creationState,
212
+ syncRequired,
213
+ mustContinue: finalMustContinue,
214
+ applyWrites: applyResults.writes.length,
215
+ applyDeletions: applyResults.deletions.length,
216
+ },
217
+ });
218
+ return {
219
+ content: [
220
+ {
221
+ type: "text",
222
+ text: JSON.stringify(payload, null, 2),
223
+ },
224
+ ],
225
+ structuredContent: payload,
226
+ };
227
+ });
228
+ };
229
+ export const registerCreateBankTool = (server, options) => {
230
+ registerCreateLikeTool(server, options, {
231
+ toolName: "create_bank",
232
+ title: "Create Project AI Guidance Bank",
233
+ description: "Create the canonical project AI Guidance Bank under the user-level AI Guidance Bank storage. AI Guidance Bank is the durable rules-and-skills layer for the project, not conversational memory.",
234
+ });
235
+ registerCreateLikeTool(server, options, {
236
+ toolName: "improve_bank",
237
+ title: "Improve Project AI Guidance Bank",
238
+ description: "Review and improve an existing project AI Guidance Bank through the guided flow. Use this when the project already has a durable rules-and-skills layer that needs refinement or expansion.",
239
+ });
240
+ };
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { ENTRY_KINDS, ENTRY_SCOPES } from "../../core/bank/types.js";
3
+ import { AbsoluteProjectPathSchema, SessionRefSchema } from "./sharedSchemas.js";
4
+ import { writeEntryAuditEvent } from "./auditUtils.js";
5
+ import { buildInvalidToolArgsResult, buildStructuredToolResult, readEntryBeforeMutation, resolveScopedMutationContext, } from "./entryMutationHelpers.js";
6
+ const DeleteEntryArgsSchema = z
7
+ .object({
8
+ scope: z.enum(ENTRY_SCOPES).describe("Delete target: shared user-level entries or project-specific entries."),
9
+ kind: z.enum(ENTRY_KINDS).describe("Whether to delete a thematic rule file or a skill folder."),
10
+ projectPath: AbsoluteProjectPathSchema,
11
+ sessionRef: SessionRefSchema,
12
+ path: z
13
+ .string()
14
+ .trim()
15
+ .min(1)
16
+ .describe("Rule file path or skill folder path relative to the selected layer."),
17
+ })
18
+ .strict();
19
+ export const registerDeleteEntryTool = (server, options) => {
20
+ server.registerTool("delete_entry", {
21
+ title: "Delete AI Guidance Bank Entry",
22
+ description: "Delete a rule file or skill folder from the shared or project AI Guidance Bank layer. Use with care and only after the user explicitly wants the entry removed.",
23
+ annotations: {
24
+ readOnlyHint: false,
25
+ destructiveHint: true,
26
+ },
27
+ inputSchema: {
28
+ scope: z.enum(ENTRY_SCOPES).describe("Delete target: shared user-level entries or project-specific entries."),
29
+ kind: z.enum(ENTRY_KINDS).describe("Whether to delete a thematic rule file or a skill folder."),
30
+ projectPath: AbsoluteProjectPathSchema,
31
+ sessionRef: SessionRefSchema,
32
+ path: z
33
+ .string()
34
+ .trim()
35
+ .min(1)
36
+ .describe("Rule file path or skill folder path relative to the selected layer."),
37
+ },
38
+ outputSchema: {
39
+ status: z.enum(["deleted", "not_found"]),
40
+ scope: z.enum(ENTRY_SCOPES),
41
+ kind: z.enum(ENTRY_KINDS),
42
+ projectId: z.string(),
43
+ projectName: z.string(),
44
+ projectPath: z.string(),
45
+ path: z.string(),
46
+ },
47
+ }, async (args) => {
48
+ const parsedArgs = DeleteEntryArgsSchema.safeParse(args);
49
+ if (!parsedArgs.success) {
50
+ return buildInvalidToolArgsResult("delete_entry", parsedArgs.error);
51
+ }
52
+ const mutationContext = await resolveScopedMutationContext({
53
+ repository: options.repository,
54
+ projectPath: parsedArgs.data.projectPath,
55
+ scope: parsedArgs.data.scope,
56
+ missingProjectMessage: "Project AI Guidance Bank does not exist yet. Call create_bank before deleting project-scoped entries.",
57
+ });
58
+ if ("isError" in mutationContext) {
59
+ return mutationContext;
60
+ }
61
+ const { identity, projectId } = mutationContext;
62
+ const beforeContent = await readEntryBeforeMutation({
63
+ repository: options.repository,
64
+ scope: parsedArgs.data.scope,
65
+ kind: parsedArgs.data.kind,
66
+ path: parsedArgs.data.path,
67
+ ...(projectId ? { projectId } : {}),
68
+ });
69
+ const result = parsedArgs.data.kind === "rules"
70
+ ? await options.repository.deleteRule(parsedArgs.data.scope, parsedArgs.data.path, projectId)
71
+ : await options.repository.deleteSkill(parsedArgs.data.scope, parsedArgs.data.path, projectId);
72
+ if (result.status === "deleted") {
73
+ await writeEntryAuditEvent({
74
+ auditLogger: options.auditLogger,
75
+ sessionRef: parsedArgs.data.sessionRef,
76
+ tool: "delete_entry",
77
+ action: "delete",
78
+ scope: parsedArgs.data.scope,
79
+ kind: parsedArgs.data.kind,
80
+ projectId: identity.projectId,
81
+ projectPath: identity.projectPath,
82
+ path: result.path,
83
+ beforeContent,
84
+ afterContent: null,
85
+ });
86
+ }
87
+ const payload = {
88
+ status: result.status,
89
+ scope: parsedArgs.data.scope,
90
+ kind: parsedArgs.data.kind,
91
+ projectId: identity.projectId,
92
+ projectName: identity.projectName,
93
+ projectPath: identity.projectPath,
94
+ path: result.path,
95
+ };
96
+ return buildStructuredToolResult(payload);
97
+ });
98
+ };
@@ -0,0 +1,120 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { discoverExistingGuidance } from "../../core/projects/discoverExistingGuidance.js";
5
+ import { resolveProjectIdentity } from "../../core/projects/identity.js";
6
+ import { ValidationError } from "../../shared/errors.js";
7
+ import { AbsoluteProjectPathSchema, SessionRefSchema } from "./sharedSchemas.js";
8
+ import { writeToolAuditEvent } from "./auditUtils.js";
9
+ import { buildInvalidToolArgsResult, buildStructuredToolResult } from "./entryMutationHelpers.js";
10
+ const DeleteGuidanceSourceArgsSchema = z
11
+ .object({
12
+ projectPath: AbsoluteProjectPathSchema,
13
+ sessionRef: SessionRefSchema,
14
+ sourcePath: z
15
+ .string()
16
+ .trim()
17
+ .min(1)
18
+ .describe("Absolute path to a discovered repository-local or provider-project guidance source."),
19
+ })
20
+ .strict();
21
+ const deleteGuidancePath = async (targetPath) => {
22
+ try {
23
+ const stats = await fs.lstat(targetPath);
24
+ if (stats.isSymbolicLink()) {
25
+ throw new ValidationError(`Guidance source cannot be a symbolic link: ${targetPath}`);
26
+ }
27
+ if (stats.isDirectory()) {
28
+ await fs.rm(targetPath, { recursive: true, force: false });
29
+ return "deleted";
30
+ }
31
+ if (stats.isFile()) {
32
+ await fs.unlink(targetPath);
33
+ return "deleted";
34
+ }
35
+ throw new ValidationError(`Unsupported guidance source path type: ${targetPath}`);
36
+ }
37
+ catch (error) {
38
+ if (error.code === "ENOENT") {
39
+ return "not_found";
40
+ }
41
+ throw error;
42
+ }
43
+ };
44
+ export const registerDeleteGuidanceSourceTool = (server, options) => {
45
+ server.registerTool("delete_guidance_source", {
46
+ title: "Delete External Guidance Source",
47
+ description: "Delete a discovered repository-local or provider-project guidance source after the user explicitly chose a move-to-Memory-Bank strategy.",
48
+ annotations: {
49
+ readOnlyHint: false,
50
+ destructiveHint: true,
51
+ },
52
+ inputSchema: {
53
+ projectPath: AbsoluteProjectPathSchema,
54
+ sessionRef: SessionRefSchema,
55
+ sourcePath: z
56
+ .string()
57
+ .trim()
58
+ .min(1)
59
+ .describe("Absolute path to a discovered repository-local or provider-project guidance source."),
60
+ },
61
+ outputSchema: {
62
+ status: z.enum(["deleted", "not_found"]),
63
+ projectId: z.string(),
64
+ projectName: z.string(),
65
+ projectPath: z.string(),
66
+ sourcePath: z.string(),
67
+ relativePath: z.string(),
68
+ kind: z.string(),
69
+ scope: z.enum(["repository-local", "provider-project"]),
70
+ provider: z.enum(["codex", "cursor", "claude"]).nullable(),
71
+ },
72
+ }, async (args) => {
73
+ const parsedArgs = DeleteGuidanceSourceArgsSchema.safeParse(args);
74
+ if (!parsedArgs.success) {
75
+ return buildInvalidToolArgsResult("delete_guidance_source", parsedArgs.error);
76
+ }
77
+ const identity = resolveProjectIdentity(parsedArgs.data.projectPath);
78
+ const discoveredSources = await discoverExistingGuidance(identity.projectPath);
79
+ const targetPath = path.resolve(parsedArgs.data.sourcePath);
80
+ const source = discoveredSources.find((candidate) => path.resolve(candidate.path) === targetPath);
81
+ if (!source) {
82
+ return {
83
+ isError: true,
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: `Guidance source is not currently discoverable for this project: ${targetPath}`,
88
+ },
89
+ ],
90
+ };
91
+ }
92
+ const status = await deleteGuidancePath(targetPath);
93
+ await writeToolAuditEvent({
94
+ auditLogger: options.auditLogger,
95
+ sessionRef: parsedArgs.data.sessionRef,
96
+ tool: "delete_guidance_source",
97
+ action: "delete_guidance",
98
+ projectId: identity.projectId,
99
+ projectPath: identity.projectPath,
100
+ details: {
101
+ status,
102
+ sourcePath: targetPath,
103
+ relativePath: source.relativePath,
104
+ scope: source.scope,
105
+ provider: source.provider,
106
+ },
107
+ });
108
+ return buildStructuredToolResult({
109
+ status,
110
+ projectId: identity.projectId,
111
+ projectName: identity.projectName,
112
+ projectPath: identity.projectPath,
113
+ sourcePath: targetPath,
114
+ relativePath: source.relativePath,
115
+ kind: source.kind,
116
+ scope: source.scope,
117
+ provider: source.provider,
118
+ });
119
+ });
120
+ };
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ import { ENTRY_KINDS, ENTRY_SCOPES } from "../../core/bank/types.js";
3
+ import { resolveProjectIdentity } from "../../core/projects/identity.js";
4
+ import { AbsoluteProjectPathSchema } from "./sharedSchemas.js";
5
+ const ListEntriesArgsSchema = z
6
+ .object({
7
+ scope: z.enum(ENTRY_SCOPES).optional().describe("Entry layer to query. Defaults to shared."),
8
+ kind: z.enum(ENTRY_KINDS).describe("Entry namespace to query. Allowed values: rules | skills."),
9
+ projectPath: AbsoluteProjectPathSchema.optional(),
10
+ group: z
11
+ .string()
12
+ .trim()
13
+ .min(1)
14
+ .optional()
15
+ .describe("Optional subdirectory path inside the selected namespace."),
16
+ })
17
+ .strict();
18
+ export const registerListEntriesTool = (server, options) => {
19
+ server.registerTool("list_entries", {
20
+ title: "List AI Guidance Bank Entries",
21
+ description: "List rule or skill files from the local AI Guidance Bank.",
22
+ annotations: {
23
+ readOnlyHint: true,
24
+ destructiveHint: false,
25
+ },
26
+ inputSchema: {
27
+ scope: z.enum(ENTRY_SCOPES).optional().describe("Entry layer to query. Defaults to shared."),
28
+ kind: z.enum(ENTRY_KINDS).describe("Entry namespace to query. Allowed values: rules | skills."),
29
+ projectPath: AbsoluteProjectPathSchema.optional(),
30
+ group: z
31
+ .string()
32
+ .trim()
33
+ .min(1)
34
+ .optional()
35
+ .describe("Optional subdirectory path inside the selected namespace."),
36
+ },
37
+ outputSchema: {
38
+ scope: z.enum(ENTRY_SCOPES),
39
+ kind: z.enum(ENTRY_KINDS),
40
+ projectPath: z.string().optional(),
41
+ group: z.string().optional(),
42
+ entries: z.array(z.object({
43
+ path: z.string(),
44
+ })),
45
+ },
46
+ }, async (args) => {
47
+ const parsedArgs = ListEntriesArgsSchema.safeParse(args);
48
+ if (!parsedArgs.success) {
49
+ return {
50
+ isError: true,
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: `Invalid arguments for tool list_entries: ${z.prettifyError(parsedArgs.error)}`,
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ const scope = parsedArgs.data.scope ?? "shared";
60
+ if (scope === "project" && parsedArgs.data.projectPath === undefined) {
61
+ return {
62
+ isError: true,
63
+ content: [
64
+ {
65
+ type: "text",
66
+ text: "projectPath is required when scope is project.",
67
+ },
68
+ ],
69
+ };
70
+ }
71
+ const projectId = scope === "project" && parsedArgs.data.projectPath
72
+ ? resolveProjectIdentity(parsedArgs.data.projectPath).projectId
73
+ : undefined;
74
+ const entries = scope === "project"
75
+ ? await options.repository.listLayerEntries("project", parsedArgs.data.kind, projectId, parsedArgs.data.group)
76
+ : await options.repository.listEntries(parsedArgs.data.kind, parsedArgs.data.group);
77
+ const payload = {
78
+ scope,
79
+ kind: parsedArgs.data.kind,
80
+ ...(parsedArgs.data.projectPath ? { projectPath: parsedArgs.data.projectPath } : {}),
81
+ ...(parsedArgs.data.group ? { group: parsedArgs.data.group } : {}),
82
+ entries,
83
+ };
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text",
88
+ text: JSON.stringify(payload, null, 2),
89
+ },
90
+ ],
91
+ structuredContent: payload,
92
+ };
93
+ });
94
+ };
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ import { ENTRY_KINDS, ENTRY_SCOPES } from "../../core/bank/types.js";
3
+ import { resolveProjectIdentity } from "../../core/projects/identity.js";
4
+ import { ValidationError } from "../../shared/errors.js";
5
+ import { AbsoluteProjectPathSchema } from "./sharedSchemas.js";
6
+ const ReadEntryArgsSchema = z
7
+ .object({
8
+ scope: z.enum(ENTRY_SCOPES).optional().describe("Entry layer to query. Defaults to shared."),
9
+ kind: z.enum(ENTRY_KINDS).describe("Entry namespace to query. Allowed values: rules | skills."),
10
+ projectPath: AbsoluteProjectPathSchema.optional(),
11
+ path: z.string().trim().min(1).describe("Relative file path inside the selected namespace."),
12
+ })
13
+ .strict();
14
+ export const registerReadEntryTool = (server, options) => {
15
+ server.registerTool("read_entry", {
16
+ title: "Read AI Guidance Bank Entry",
17
+ description: "Read a rule or skill file from the local AI Guidance Bank.",
18
+ annotations: {
19
+ readOnlyHint: true,
20
+ destructiveHint: false,
21
+ },
22
+ inputSchema: {
23
+ scope: z.enum(ENTRY_SCOPES).optional().describe("Entry layer to query. Defaults to shared."),
24
+ kind: z.enum(ENTRY_KINDS).describe("Entry namespace to query. Allowed values: rules | skills."),
25
+ projectPath: AbsoluteProjectPathSchema.optional(),
26
+ path: z.string().trim().min(1).describe("Relative file path inside the selected namespace."),
27
+ },
28
+ outputSchema: {
29
+ scope: z.enum(ENTRY_SCOPES),
30
+ kind: z.enum(ENTRY_KINDS),
31
+ projectPath: z.string().optional(),
32
+ path: z.string(),
33
+ content: z.string(),
34
+ },
35
+ }, async (args) => {
36
+ const parsedArgs = ReadEntryArgsSchema.safeParse(args);
37
+ if (!parsedArgs.success) {
38
+ return {
39
+ isError: true,
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: `Invalid arguments for tool read_entry: ${z.prettifyError(parsedArgs.error)}`,
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ try {
49
+ const scope = parsedArgs.data.scope ?? "shared";
50
+ if (scope === "project" && parsedArgs.data.projectPath === undefined) {
51
+ return {
52
+ isError: true,
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: "projectPath is required when scope is project.",
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ const projectId = scope === "project" && parsedArgs.data.projectPath
62
+ ? resolveProjectIdentity(parsedArgs.data.projectPath).projectId
63
+ : undefined;
64
+ const content = scope === "project"
65
+ ? await options.repository.readLayerEntry("project", parsedArgs.data.kind, parsedArgs.data.path, projectId)
66
+ : await options.repository.readEntry(parsedArgs.data.kind, parsedArgs.data.path);
67
+ const payload = {
68
+ scope,
69
+ kind: parsedArgs.data.kind,
70
+ ...(parsedArgs.data.projectPath ? { projectPath: parsedArgs.data.projectPath } : {}),
71
+ path: parsedArgs.data.path,
72
+ content,
73
+ };
74
+ return {
75
+ content: [
76
+ {
77
+ type: "text",
78
+ text: content,
79
+ },
80
+ ],
81
+ structuredContent: payload,
82
+ };
83
+ }
84
+ catch (error) {
85
+ if (error instanceof ValidationError) {
86
+ return {
87
+ isError: true,
88
+ content: [
89
+ {
90
+ type: "text",
91
+ text: error.message,
92
+ },
93
+ ],
94
+ };
95
+ }
96
+ throw error;
97
+ }
98
+ });
99
+ };