@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,294 @@
1
+ import { CREATE_FLOW_COMPLETED_ITERATION, requiresCreateFlowStepOutcome } from "./createFlowPhases.js";
2
+ import { renderCreateDeriveGuidance } from "./createBankDeriveGuidance/index.js";
3
+ import { formatGuidanceSourceStrategy, } from "./guidanceStrategies.js";
4
+ const STABLE_CONTRACT_NOTE = `Use \`phase\` as the main guide for the current create step and treat \`iteration\` as diagnostic only. If \`creationPrompt\` is present, use it as the stable create-flow contract; this step prompt contains only the incremental instruction for the current phase.`;
5
+ const renderExistingBankBaselineSection = (hasExistingProjectBank, currentBankSnapshot) => hasExistingProjectBank
6
+ ? `## Current Bank Baseline
7
+
8
+ A project AI Guidance Bank already exists for this repository. Treat the current project bank as the canonical baseline and improve it instead of recreating it blindly.
9
+
10
+ - Current project bank inventory: ${currentBankSnapshot.entries.length} entr${currentBankSnapshot.entries.length === 1 ? "y" : "ies"}.
11
+ - Reuse strong existing entries
12
+ - Prefer updating or replacing weak entries over duplicating them
13
+ - Remove stale or overlapping entries only when there is clear evidence and the user approves destructive changes
14
+ - Use \`list_entries\` and \`read_entry\` with \`scope: "project"\` and the current \`projectPath\` when you need the full text of an existing project-bank entry
15
+ `
16
+ : "";
17
+ const buildContinuationOutcomeInstruction = (iteration) => {
18
+ if (!requiresCreateFlowStepOutcome(iteration)) {
19
+ return "";
20
+ }
21
+ const baseInstruction = " Also provide an explicit result for this content phase: use `create_bank.apply` for changes or set `stepOutcome` to `applied` or `no_changes`.";
22
+ if (iteration === 3) {
23
+ return `${baseInstruction} If you use \`no_changes\`, use \`stepOutcomeNote\` to name the strongest remaining candidates you reviewed and why they were skipped.`;
24
+ }
25
+ if (iteration === 4) {
26
+ return `${baseInstruction} If you use \`no_changes\`, use \`stepOutcomeNote\` to summarize the strongest skipped or already-covered candidates and why the bank is complete enough.`;
27
+ }
28
+ return `${baseInstruction} If you use \`no_changes\`, include \`stepOutcomeNote\`.`;
29
+ };
30
+ const appendContinuationInstruction = (prompt, iteration) => {
31
+ const continuationSuffix = buildContinuationOutcomeInstruction(iteration);
32
+ return `${prompt}
33
+
34
+ ## Continuation
35
+
36
+ After completing this step, call \`create_bank\` again with \`iteration: ${iteration + 1}\` and \`stepCompleted: true\`.${continuationSuffix}`;
37
+ };
38
+ const renderDiscoveredSourcesSection = (discoveredSources) => {
39
+ if (discoveredSources.length === 0) {
40
+ return `## Discovered Guidance Sources
41
+
42
+ No repository-local or provider-project guidance sources were discovered for this project.`;
43
+ }
44
+ return `## Discovered Guidance Sources
45
+
46
+ ${discoveredSources
47
+ .map((source) => `- [${source.kind}${source.scope === "provider-project" && source.provider ? `/${source.provider}` : ""}] ${source.relativePath} (${source.entryType}, ${source.scope})`)
48
+ .join("\n")}`;
49
+ };
50
+ const renderConfirmedSourceStrategiesSection = (confirmedSourceStrategies) => {
51
+ if (confirmedSourceStrategies.length === 0) {
52
+ return `## Confirmed Source Decisions
53
+
54
+ No confirmed source decisions are stored yet for this flow.`;
55
+ }
56
+ return `## Confirmed Source Decisions
57
+
58
+ ${confirmedSourceStrategies
59
+ .map((strategy) => `- ${strategy.sourceRef} -> ${formatGuidanceSourceStrategy(strategy.strategy)}${strategy.note ? ` (${strategy.note})` : ""}`)
60
+ .join("\n")}`;
61
+ };
62
+ const renderDetectedStacksSection = (detectedStacks) => detectedStacks.length === 0
63
+ ? `## Detected Stacks
64
+
65
+ - other`
66
+ : `## Detected Stacks
67
+
68
+ ${detectedStacks.map((stack) => `- ${stack}`).join("\n")}`;
69
+ const renderReferenceProjectsSection = (selectedReferenceProjects) => {
70
+ if (selectedReferenceProjects.length === 0) {
71
+ return `## Reference Projects
72
+
73
+ No reference project banks were selected for this run.`;
74
+ }
75
+ return `## Reference Projects
76
+
77
+ ${selectedReferenceProjects
78
+ .map((project) => `- ${project.projectName}
79
+ - Project path: \`${project.projectPath}\`
80
+ - Shared stacks: ${project.sharedStacks.join(", ")}`)
81
+ .join("\n")}`;
82
+ };
83
+ const buildKickoffPrompt = ({ projectName, projectPath, projectBankPath, rulesDirectory, skillsDirectory, detectedStacks, selectedReferenceProjects, }) => `# Create Flow Kickoff
84
+
85
+ ${STABLE_CONTRACT_NOTE}
86
+
87
+ Project:
88
+ - \`${projectName}\`
89
+ - \`${projectPath}\`
90
+
91
+ Target AI Guidance Bank:
92
+ - \`${projectBankPath}\`
93
+ - Rules: \`${rulesDirectory}\`
94
+ - Skills: \`${skillsDirectory}\`
95
+
96
+ ${renderDetectedStacksSection(detectedStacks)}
97
+
98
+ ${renderReferenceProjectsSection(selectedReferenceProjects)}
99
+
100
+ What to do in this step:
101
+ - inspect the repository and selected reference projects
102
+ - form a candidate list for the first high-value rules and skills
103
+ - start writing only when the evidence is already strong
104
+ - delay external guidance import or deletion until the dedicated review step
105
+ - treat AI Guidance Bank as durable, reusable rules-and-skills guidance across sessions
106
+ - do not stop at a thin summary if the repository clearly supports a richer bank
107
+
108
+ Step output:
109
+ - short list of created, updated, or planned files
110
+ - short purpose for each item
111
+ - strongest remaining candidates or uncertainties to handle next`;
112
+ const buildReviewExistingPrompt = (projectPath, discoveredSources) => `# Existing Guidance Review
113
+
114
+ ${STABLE_CONTRACT_NOTE}
115
+
116
+ Review available external guidance before importing anything into AI Guidance Bank.
117
+
118
+ Project path:
119
+ - \`${projectPath}\`
120
+
121
+ ${renderDiscoveredSourcesSection(discoveredSources)}
122
+
123
+ What to do:
124
+ - Treat the listed repository-local and provider-project sources as the guaranteed inputs for this review
125
+ - Skip purely empty, obsolete, or trivial sources without bothering the user
126
+ - By default, do not expose internal strategy labels to the user
127
+ - Treat AI Guidance Bank as the durable canonical rules-and-skills layer for the project
128
+ - Ask for one simple confirmation:
129
+ - \`ok\`: make AI Guidance Bank the canonical source for this project and migrate useful guidance by the default policy
130
+ - \`not ok\`: leave legacy guidance untouched and do not treat it as canonical AI Guidance Bank coverage
131
+ - When the simple confirmation is enough, advance with \`sourceReviewDecision: "ok"\` or \`sourceReviewDecision: "not_ok"\`
132
+ - Ask a more detailed follow-up only when deletion is risky enough that the simple confirmation is not safe
133
+ - Keep the user-facing review short and action-oriented:
134
+ - start with a 1-2 sentence summary of what sources were found
135
+ - recommend one default action
136
+ - end with one explicit CTA question telling the user to answer \`ok\` or \`not ok\`
137
+ - avoid long protocol dumps, source-strategy labels, or repeating the same source list multiple times
138
+
139
+ Decision rules:
140
+ - Treat provider-project guidance as legacy project-specific input that usually needs review or migration
141
+ - Never delete or rewrite any original source during this review step`;
142
+ const buildImportSelectedPrompt = (discoveredSources, confirmedSourceStrategies) => `# Import Selected Guidance
143
+
144
+ ${STABLE_CONTRACT_NOTE}
145
+
146
+ Apply the source-level strategies the user approved for external guidance.
147
+
148
+ ${renderDiscoveredSourcesSection(discoveredSources)}
149
+
150
+ ${renderConfirmedSourceStrategiesSection(confirmedSourceStrategies)}
151
+
152
+ What to do:
153
+ - Treat the confirmed source decisions below as the internal execution plan for this import step
154
+ - Convert approved guidance into canonical AI Guidance Bank rules and skills
155
+ - Keep imported content durable, operational, and reusable across future sessions
156
+ - Split entries between project scope and shared scope when appropriate
157
+ - Assign stable ids, titles, topics, and stacks
158
+ - Deduplicate against existing AI Guidance Bank content before writing
159
+ - Use \`create_bank\` with an \`apply\` payload for batched canonical writes and deletions during this flow
160
+ - In \`create_bank.apply\`, paths must be relative to the rules/skills root only; use \`topics/example.md\` or \`adding-feature\`, not \`rules/topics/example.md\` or \`skills/adding-feature\`
161
+ - If the user simply confirmed \`ok\`, follow the default policy: make AI Guidance Bank canonical, migrate useful file-level guidance, ignore empty or container-only sources automatically, and clean up migrated legacy files when it is safe to do so after successful writes and verification
162
+ - If the user confirmed \`not ok\`, still migrate useful guidance into the canonical AI Guidance Bank but leave the legacy sources untouched even if that creates temporary duplication
163
+ - When replacing or deleting an existing AI Guidance Bank entry, read it first and pass its \`sha256\` back as \`baseSha256\`
164
+ - If \`create_bank.apply\` reports a \`conflict\`, re-read the affected entry, rebuild the full final document, and retry with the fresh \`baseSha256\`
165
+
166
+ Write rules:
167
+ - Create a \`rule\` when the source describes a stable constraint, convention, or preference
168
+ - Create a \`skill\` when the source describes a reusable workflow or task sequence
169
+ - Prefer a small number of high-value entries over fragmented boilerplate
170
+ - If a source duplicates existing canonical content, update or skip instead of cloning it
171
+
172
+ Safety rules:
173
+ - Do not delete, rewrite, or trim any original source unless the confirmed review decision allows cleanup and the migration was already written and verified successfully
174
+ - If the user did not clearly approve an action for a source, leave that source untouched
175
+ - If one source mixes project-specific and shared material, split it across scopes instead of forcing one destination
176
+ - Do not count provider-local or provider-global guidance as existing AI Guidance Bank coverage when deciding what still needs to be written`;
177
+ const buildDeriveFromProjectPrompt = (projectPath, detectedStacks) => `# Derive From Project
178
+
179
+ ${STABLE_CONTRACT_NOTE}
180
+
181
+ Derive additional AI Guidance Bank entries from the real repository.
182
+
183
+ Project path:
184
+ - \`${projectPath}\`
185
+
186
+ What to do:
187
+ - Inspect the real repository directly: project structure, entrypoints, configuration, source files, and recurring implementation patterns
188
+ - Create a focused set of high-value project rules and skills
189
+ - Prefer stable patterns over one-off details
190
+ - Keep AI Guidance Bank focused on durable guidance that remains useful across future sessions
191
+ - Put reusable cross-project guidance into shared scope only when the evidence is strong
192
+ - Review the strongest remaining candidates before a major batch
193
+ - Treat the bank as incomplete if obvious entries are still missing without a clear skip reason
194
+
195
+ Quality rules:
196
+ - Do not rely on a server-provided file checklist; gather your own evidence from the real repository
197
+ - Prefer patterns confirmed by multiple files, configuration, or stable architecture boundaries
198
+ - Skip temporary, noisy, or accidental implementation details
199
+ - If a candidate rule is high-impact and your confidence is low, ask the user before writing it
200
+ - Apply derived changes through \`create_bank.apply\` in batches instead of a long series of one-entry write calls
201
+ - In \`create_bank.apply\`, keep each path relative to the rules/skills root instead of prefixing it with \`rules/\` or \`skills/\`
202
+ - If \`create_bank.apply\` reports a \`conflict\`, re-read the affected entry, rebuild the full final document, and retry with the fresh \`baseSha256\`
203
+ - For each obvious candidate you skip, keep a short reason: already covered, weak evidence, intentionally deferred, or better suited to shared scope
204
+
205
+ ${renderCreateDeriveGuidance(detectedStacks)}`;
206
+ const buildFinalizePrompt = () => `# Finalize AI Guidance Bank
207
+
208
+ ${STABLE_CONTRACT_NOTE}
209
+
210
+ Finish the project AI Guidance Bank creation flow.
211
+
212
+ What to do:
213
+ - Deduplicate overlapping rules and skills
214
+ - Verify scope split between shared and project entries
215
+ - Check ids, titles, topics, and stacks for consistency
216
+ - Keep only durable guidance that should survive across sessions; leave conversational context out
217
+ - If confidence is low for any high-impact rule, ask the user before keeping it
218
+ - Use \`create_bank.apply\` for the final cleanup batch when you need to replace or delete multiple entries
219
+ - If \`create_bank.apply\` reports a \`conflict\`, re-read the affected entry, rebuild the final canonical document, and retry the cleanup batch with fresh \`baseSha256\`
220
+ - Return a concise completion report when the bank is in a good canonical state
221
+ - Run an explicit gap-and-coverage review before declaring the bank done
222
+
223
+ Final pass checklist:
224
+ - Remove near-duplicate entries and merge them into the clearest canonical version
225
+ - Ensure each entry is either clearly a \`rule\` or clearly a \`skill\`
226
+ - Ensure project overrides do not duplicate shared guidance without adding real specificity
227
+ - Leave unresolved or low-confidence items out unless the user explicitly approves them
228
+ - Confirm the bank is not materially poorer than the strongest project evidence from this run
229
+ - Check the strongest applicable topic and skill candidates, then create them, merge them, or record a skip reason
230
+ - In the final report, mention imported sources, newly derived entries, and any important skipped uncertainties or intentionally omitted candidates
231
+ - If you finish with \`stepOutcome: "no_changes"\`, use \`stepOutcomeNote\` to summarize the strongest skipped or already-covered high-value candidates and why no further mutation was needed`;
232
+ const buildCompletedPrompt = () => `# Create Flow Completed
233
+
234
+ The iterative project AI Guidance Bank creation flow is complete.
235
+
236
+ What to do:
237
+ - Do not continue the create flow automatically
238
+ - Re-enter the flow only if the user explicitly asks for another create pass or wants to restart parts of the review
239
+ - Continue normal AI Guidance Bank work through the standard mutation tools when the user asks for targeted updates`;
240
+ const CREATE_FLOW_PROMPT_BUILDERS = [
241
+ ({ projectName, projectPath, projectBankPath, rulesDirectory, skillsDirectory, detectedStacks, selectedReferenceProjects }) => buildKickoffPrompt({
242
+ projectName,
243
+ projectPath,
244
+ projectBankPath,
245
+ rulesDirectory,
246
+ skillsDirectory,
247
+ detectedStacks,
248
+ selectedReferenceProjects,
249
+ }),
250
+ ({ projectPath, discoveredSources }) => buildReviewExistingPrompt(projectPath, discoveredSources),
251
+ ({ discoveredSources, confirmedSourceStrategies }) => buildImportSelectedPrompt(discoveredSources, confirmedSourceStrategies),
252
+ ({ projectPath, detectedStacks }) => buildDeriveFromProjectPrompt(projectPath, detectedStacks),
253
+ () => buildFinalizePrompt(),
254
+ () => buildCompletedPrompt(),
255
+ ];
256
+ export const buildReadyProjectBankPrompt = ({ updatedAt, updatedDaysAgo, }) => {
257
+ const updatedLine = updatedAt === null || updatedDaysAgo === null
258
+ ? "A project AI Guidance Bank already exists for this repository."
259
+ : `A project AI Guidance Bank already exists for this repository and was last updated ${updatedDaysAgo} day${updatedDaysAgo === 1 ? "" : "s"} ago (${updatedAt}).`;
260
+ return `# Existing Project AI Guidance Bank
261
+
262
+ ${updatedLine}
263
+
264
+ What to do:
265
+ - Tell the user that a project AI Guidance Bank already exists for this repository
266
+ - Ask whether they want to improve it now instead of keeping it as-is
267
+ - If the user wants to improve it, call \`create_bank\` again with \`iteration: 1\`
268
+ - If the user does not want to improve it, continue normal work with the current ready bank through \`resolve_context\`
269
+ - If you continue into later create iterations, treat the existing project bank as the canonical baseline and improve gaps, stale entries, duplicates, and weak coverage instead of recreating the bank from scratch`;
270
+ };
271
+ export const buildCreateBankIterationPrompt = ({ iteration, projectName, projectPath, projectBankPath, rulesDirectory, skillsDirectory, detectedStacks, selectedReferenceProjects, discoveredSources, confirmedSourceStrategies, currentBankSnapshot, hasExistingProjectBank = false, }) => {
272
+ const normalizedIteration = Math.min(Math.max(iteration, 0), CREATE_FLOW_COMPLETED_ITERATION);
273
+ const buildPrompt = CREATE_FLOW_PROMPT_BUILDERS[normalizedIteration];
274
+ const prompt = buildPrompt({
275
+ iteration,
276
+ projectName,
277
+ projectPath,
278
+ projectBankPath,
279
+ rulesDirectory,
280
+ skillsDirectory,
281
+ detectedStacks,
282
+ selectedReferenceProjects,
283
+ discoveredSources,
284
+ confirmedSourceStrategies,
285
+ currentBankSnapshot,
286
+ hasExistingProjectBank,
287
+ });
288
+ const promptWithBaseline = hasExistingProjectBank && normalizedIteration > 0 && normalizedIteration < CREATE_FLOW_COMPLETED_ITERATION
289
+ ? `${renderExistingBankBaselineSection(true, currentBankSnapshot)}\n${prompt}`
290
+ : prompt;
291
+ return normalizedIteration < CREATE_FLOW_COMPLETED_ITERATION
292
+ ? appendContinuationInstruction(promptWithBaseline, normalizedIteration)
293
+ : promptWithBaseline;
294
+ };
@@ -0,0 +1,95 @@
1
+ import { DETECTABLE_STACKS } from "../context/types.js";
2
+ const renderDetectedStackSection = (detectedStacks) => {
3
+ if (detectedStacks.length === 1 && detectedStacks[0] === "other") {
4
+ return `## Detected Stack
5
+
6
+ No specific stack signals were detected confidently. Use \`other\` as the fallback stack and infer only project-supported patterns from the codebase.`;
7
+ }
8
+ return `## Detected Stack
9
+
10
+ ${detectedStacks.map((stack) => `- ${stack}`).join("\n")}`;
11
+ };
12
+ const renderSupportedStackIdsSection = () => `## Supported Stack Ids
13
+
14
+ Use only these canonical stack ids in AI Guidance Bank metadata:
15
+ ${DETECTABLE_STACKS.map((stack) => `- ${stack}`).join("\n")}
16
+
17
+ If no specific stack fits confidently, use \`other\`.`;
18
+ const renderReferenceProjectsSection = (selectedReferenceProjects) => {
19
+ if (selectedReferenceProjects.length === 0) {
20
+ return `## Reference Projects
21
+
22
+ No reference project banks were selected for this run.`;
23
+ }
24
+ return `## Reference Projects
25
+
26
+ ${selectedReferenceProjects
27
+ .map((project) => `- ${project.projectName}
28
+ - Project path: \`${project.projectPath}\`
29
+ - Shared stacks: ${project.sharedStacks.join(", ")}`)
30
+ .join("\n")}`;
31
+ };
32
+ export const buildCreateBankPrompt = ({ projectName, projectPath, projectBankPath, rulesDirectory, skillsDirectory, detectedStacks, selectedReferenceProjects, }) => `# Project AI Guidance Bank Creation
33
+
34
+ You are creating the canonical AI Guidance Bank for \`${projectName}\`.
35
+
36
+ Project path:
37
+ - \`${projectPath}\`
38
+
39
+ Target AI Guidance Bank:
40
+ - \`${projectBankPath}\`
41
+ - Rules root: \`${rulesDirectory}\`
42
+ - Skills root: \`${skillsDirectory}\`
43
+
44
+ ${renderDetectedStackSection(detectedStacks)}
45
+
46
+ ${renderSupportedStackIdsSection()}
47
+
48
+ ${renderReferenceProjectsSection(selectedReferenceProjects)}
49
+
50
+ ## Stable Contract
51
+
52
+ - AI Guidance Bank is the canonical user-managed guidance layer for this project
53
+ - AI Guidance Bank stores durable rules, skills, and reusable project guidance across sessions
54
+ - Use \`phase\` as the primary guide during the create/improve flow; treat \`iteration\` as diagnostic only
55
+ - Use real project code, shared AI Guidance Bank context, selected reference projects, and explicit user instructions as the main inputs
56
+ - External repository-local or provider-project guidance must be reviewed explicitly in later steps before import
57
+ - Provider-local skills, provider-global skills, and model-native instructions may help analysis, but they never count as canonical AI Guidance Bank coverage
58
+
59
+ ## Writing Contract
60
+
61
+ - During the guided flow, prefer batched writes through \`create_bank.apply\`
62
+ - Pass complete final documents, not partial markdown patches
63
+ - \`create_bank.apply.path\` must be relative to the rules/skills root:
64
+ - rules: \`core/general.md\`, \`topics/architecture.md\`
65
+ - skills: \`adding-feature\`, \`task-based-reading\`
66
+ - Do not prefix apply paths with \`rules/\` or \`skills/\`
67
+ - When replacing or deleting an existing entry, read it first and pass \`baseSha256\`
68
+ - If \`create_bank.apply\` reports a conflict, re-read the affected entry and retry with a fresh \`baseSha256\`
69
+ - Reserve \`upsert_rule\`, \`upsert_skill\`, and \`delete_entry\` for targeted edits outside the full create/improve flow
70
+
71
+ ## Scope Rules
72
+
73
+ - Put guidance in the project bank only when it reflects stable patterns from this repository or meaningfully refines shared guidance
74
+ - Put clearly reusable cross-project guidance into the shared layer instead of the project layer
75
+ - Only shared/project AI Guidance Bank entries and explicitly reviewed external sources count as canonical coverage
76
+
77
+ ## Coverage Expectations
78
+
79
+ - Create a right-sized bank, not a thin summary
80
+ - Consider both rules and skills
81
+ - Build a candidate list before the first substantial write batch
82
+ - If obvious candidates are skipped, keep a clear reason and reflect it later in \`stepOutcomeNote\`
83
+
84
+ Expected Bank Density:
85
+ - 2-6 focused rule files when project evidence supports them
86
+ - 2-5 focused skills when reusable workflows are clearly present
87
+
88
+ ## Kickoff Expectations
89
+
90
+ During the initial step:
91
+ - inspect the repository and selected reference projects
92
+ - build a broad candidate inventory before the first major write batch
93
+ - do not import or delete external guidance yet; that happens in later review/import steps
94
+ - do not stop after the first acceptable batch if the project clearly supports stronger canonical coverage
95
+ `;
@@ -0,0 +1,28 @@
1
+ export const CREATE_FLOW_PHASES = [
2
+ "sync_required",
3
+ "ready_to_improve",
4
+ "kickoff",
5
+ "review_existing_guidance",
6
+ "import_selected_guidance",
7
+ "derive_from_project",
8
+ "finalize",
9
+ "completed",
10
+ ];
11
+ export const CREATE_ITERATION_PHASES = [
12
+ "kickoff",
13
+ "review_existing_guidance",
14
+ "import_selected_guidance",
15
+ "derive_from_project",
16
+ "finalize",
17
+ "completed",
18
+ ];
19
+ export const CREATE_FLOW_COMPLETED_ITERATION = CREATE_ITERATION_PHASES.length - 1;
20
+ const CREATE_FLOW_OUTCOME_REQUIRED_PHASES = [
21
+ "import_selected_guidance",
22
+ "derive_from_project",
23
+ "finalize",
24
+ ];
25
+ export const getCreateFlowPhase = (iteration) => CREATE_ITERATION_PHASES[Math.min(Math.max(iteration, 0), CREATE_FLOW_COMPLETED_ITERATION)];
26
+ export const getNextCreateFlowIteration = (iteration) => iteration < CREATE_FLOW_COMPLETED_ITERATION ? iteration + 1 : null;
27
+ export const isCreateFlowComplete = (iteration) => iteration >= CREATE_FLOW_COMPLETED_ITERATION;
28
+ export const requiresCreateFlowStepOutcome = (iteration) => CREATE_FLOW_OUTCOME_REQUIRED_PHASES.includes(getCreateFlowPhase(iteration));
@@ -0,0 +1,43 @@
1
+ import { createHash } from "node:crypto";
2
+ import { parseCanonicalRuleDocument, parseCanonicalSkillDocument } from "../bank/canonicalEntry.js";
3
+ const sha256 = (content) => createHash("sha256").update(content).digest("hex");
4
+ export const discoverCurrentProjectBank = async (repository, projectId, exists) => {
5
+ if (!exists) {
6
+ return {
7
+ exists: false,
8
+ entries: [],
9
+ };
10
+ }
11
+ const [ruleEntries, skillEntries] = await Promise.all([
12
+ repository.listLayerEntries("project", "rules", projectId),
13
+ repository.listLayerEntries("project", "skills", projectId),
14
+ ]);
15
+ const [ruleSnapshots, skillSnapshots] = await Promise.all([
16
+ Promise.all(ruleEntries.map(async ({ path }) => {
17
+ const content = await repository.readLayerEntry("project", "rules", path, projectId);
18
+ const document = parseCanonicalRuleDocument(content);
19
+ return {
20
+ kind: "rules",
21
+ scope: "project",
22
+ path,
23
+ id: document.frontmatter.id,
24
+ sha256: sha256(content),
25
+ };
26
+ })),
27
+ Promise.all(skillEntries.map(async ({ path }) => {
28
+ const content = await repository.readLayerEntry("project", "skills", path, projectId);
29
+ const document = parseCanonicalSkillDocument(content);
30
+ return {
31
+ kind: "skills",
32
+ scope: "project",
33
+ path,
34
+ id: document.frontmatter.id,
35
+ sha256: sha256(content),
36
+ };
37
+ })),
38
+ ]);
39
+ return {
40
+ exists: true,
41
+ entries: [...ruleSnapshots, ...skillSnapshots].sort((left, right) => left.path.localeCompare(right.path)),
42
+ };
43
+ };
@@ -0,0 +1,99 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ import { discoverProviderProjectGuidance } from "./providerProjectGuidance.js";
4
+ const fileCandidates = [
5
+ { kind: "agents", relativePath: "AGENTS.md" },
6
+ { kind: "claude-md", relativePath: "CLAUDE.md" },
7
+ { kind: "claude-md", relativePath: "claude.md" },
8
+ { kind: "copilot", relativePath: "copilot-instructions.md" },
9
+ { kind: "copilot", relativePath: ".github/copilot-instructions.md" },
10
+ ];
11
+ const directoryCandidates = [
12
+ { kind: "cursor", relativePath: ".cursor" },
13
+ { kind: "claude", relativePath: ".claude" },
14
+ { kind: "codex", relativePath: ".codex" },
15
+ ];
16
+ const pathExists = async (targetPath) => {
17
+ try {
18
+ await fs.access(targetPath);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ };
25
+ const listFilesRecursively = async (directoryPath) => {
26
+ const directoryEntries = await fs.readdir(directoryPath, { withFileTypes: true });
27
+ const filePaths = [];
28
+ for (const directoryEntry of directoryEntries.sort((left, right) => left.name.localeCompare(right.name))) {
29
+ const entryPath = path.join(directoryPath, directoryEntry.name);
30
+ if (directoryEntry.isDirectory()) {
31
+ filePaths.push(...(await listFilesRecursively(entryPath)));
32
+ continue;
33
+ }
34
+ if (directoryEntry.isFile()) {
35
+ filePaths.push(entryPath);
36
+ }
37
+ }
38
+ return filePaths;
39
+ };
40
+ export const discoverExistingGuidance = async (projectPath) => {
41
+ const resolvedProjectPath = path.resolve(projectPath);
42
+ const discoveredSources = [];
43
+ for (const candidate of fileCandidates) {
44
+ const candidatePath = path.join(resolvedProjectPath, candidate.relativePath);
45
+ if (!(await pathExists(candidatePath))) {
46
+ continue;
47
+ }
48
+ discoveredSources.push({
49
+ kind: candidate.kind,
50
+ entryType: "file",
51
+ scope: "repository-local",
52
+ provider: null,
53
+ path: candidatePath,
54
+ relativePath: candidate.relativePath,
55
+ });
56
+ }
57
+ for (const candidate of directoryCandidates) {
58
+ const candidatePath = path.join(resolvedProjectPath, candidate.relativePath);
59
+ if (!(await pathExists(candidatePath))) {
60
+ continue;
61
+ }
62
+ discoveredSources.push({
63
+ kind: candidate.kind,
64
+ entryType: "directory",
65
+ scope: "repository-local",
66
+ provider: null,
67
+ path: candidatePath,
68
+ relativePath: candidate.relativePath,
69
+ });
70
+ const nestedFilePaths = await listFilesRecursively(candidatePath);
71
+ for (const nestedFilePath of nestedFilePaths) {
72
+ discoveredSources.push({
73
+ kind: candidate.kind,
74
+ entryType: "file",
75
+ scope: "repository-local",
76
+ provider: null,
77
+ path: nestedFilePath,
78
+ relativePath: path.relative(resolvedProjectPath, nestedFilePath),
79
+ });
80
+ }
81
+ }
82
+ const providerProjectSources = await discoverProviderProjectGuidance(resolvedProjectPath);
83
+ for (const source of providerProjectSources) {
84
+ const kind = source.provider === "codex"
85
+ ? "codex-project"
86
+ : source.provider === "cursor"
87
+ ? "cursor-project"
88
+ : "claude-project";
89
+ discoveredSources.push({
90
+ kind,
91
+ entryType: source.entryType,
92
+ scope: "provider-project",
93
+ provider: source.provider,
94
+ path: source.path,
95
+ relativePath: source.relativePath,
96
+ });
97
+ }
98
+ return discoveredSources.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
99
+ };
@@ -0,0 +1,87 @@
1
+ import path from "node:path";
2
+ import { promises as fs } from "node:fs";
3
+ const topLevelDirectoryCandidates = ["src", "app", "pages", "lib", "server", "packages", "scripts", "test", "tests", "docs"];
4
+ const configFileCandidates = [
5
+ "package.json",
6
+ "tsconfig.json",
7
+ "tsconfig.app.json",
8
+ "tsconfig.base.json",
9
+ "angular.json",
10
+ "eslint.config.js",
11
+ "eslint.config.mjs",
12
+ "eslint.config.cjs",
13
+ "vite.config.ts",
14
+ "vite.config.js",
15
+ "vite.config.mjs",
16
+ "next.config.ts",
17
+ "next.config.js",
18
+ "next.config.mjs",
19
+ ];
20
+ const rootDocCandidates = ["README.md", "README.mdx", "docs.md"];
21
+ const pathExists = async (targetPath) => {
22
+ try {
23
+ await fs.access(targetPath);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ };
30
+ const listMarkdownFilesRecursively = async (directoryPath) => {
31
+ const directoryEntries = await fs.readdir(directoryPath, { withFileTypes: true });
32
+ const filePaths = [];
33
+ for (const directoryEntry of directoryEntries.sort((left, right) => left.name.localeCompare(right.name))) {
34
+ const entryPath = path.join(directoryPath, directoryEntry.name);
35
+ if (directoryEntry.isDirectory()) {
36
+ filePaths.push(...(await listMarkdownFilesRecursively(entryPath)));
37
+ continue;
38
+ }
39
+ if (directoryEntry.isFile() && /\.(md|mdx)$/iu.test(directoryEntry.name)) {
40
+ filePaths.push(entryPath);
41
+ }
42
+ }
43
+ return filePaths;
44
+ };
45
+ export const discoverProjectEvidence = async (projectPath) => {
46
+ const resolvedProjectPath = path.resolve(projectPath);
47
+ const topLevelDirectories = [];
48
+ const evidenceFiles = [];
49
+ for (const directoryName of topLevelDirectoryCandidates) {
50
+ const candidatePath = path.join(resolvedProjectPath, directoryName);
51
+ if (await pathExists(candidatePath)) {
52
+ topLevelDirectories.push(directoryName);
53
+ }
54
+ }
55
+ for (const fileName of configFileCandidates) {
56
+ const candidatePath = path.join(resolvedProjectPath, fileName);
57
+ if (await pathExists(candidatePath)) {
58
+ evidenceFiles.push({
59
+ kind: "config",
60
+ relativePath: fileName,
61
+ });
62
+ }
63
+ }
64
+ for (const fileName of rootDocCandidates) {
65
+ const candidatePath = path.join(resolvedProjectPath, fileName);
66
+ if (await pathExists(candidatePath)) {
67
+ evidenceFiles.push({
68
+ kind: "doc",
69
+ relativePath: fileName,
70
+ });
71
+ }
72
+ }
73
+ const docsDirectoryPath = path.join(resolvedProjectPath, "docs");
74
+ if (await pathExists(docsDirectoryPath)) {
75
+ const docsFilePaths = await listMarkdownFilesRecursively(docsDirectoryPath);
76
+ for (const docsFilePath of docsFilePaths.slice(0, 12)) {
77
+ evidenceFiles.push({
78
+ kind: "doc",
79
+ relativePath: path.relative(resolvedProjectPath, docsFilePath),
80
+ });
81
+ }
82
+ }
83
+ return {
84
+ topLevelDirectories: topLevelDirectories.sort((left, right) => left.localeCompare(right)),
85
+ evidenceFiles: evidenceFiles.sort((left, right) => left.relativePath.localeCompare(right.relativePath)),
86
+ };
87
+ };
@@ -0,0 +1,28 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export const discoverRecentCommits = async (projectPath) => {
5
+ try {
6
+ const { stdout } = await execFileAsync("git", ["-C", projectPath, "log", "-n", "5", "--pretty=format:%h%x09%s"], {
7
+ cwd: projectPath,
8
+ });
9
+ return stdout
10
+ .split("\n")
11
+ .map((line) => line.trim())
12
+ .filter(Boolean)
13
+ .map((line) => {
14
+ const [shortHash, ...subjectParts] = line.split("\t");
15
+ if (!shortHash) {
16
+ return null;
17
+ }
18
+ return {
19
+ shortHash,
20
+ subject: subjectParts.join("\t"),
21
+ };
22
+ })
23
+ .filter((commit) => commit !== null && commit.subject.length > 0);
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ };