brainclaw 0.19.6 → 0.19.10

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 (51) hide show
  1. package/README.md +225 -126
  2. package/dist/cli.js +8 -3
  3. package/dist/commands/accept.js +102 -104
  4. package/dist/commands/add-step.js +3 -5
  5. package/dist/commands/bootstrap.js +72 -3
  6. package/dist/commands/capability.js +3 -5
  7. package/dist/commands/claim.js +14 -12
  8. package/dist/commands/complete-step.js +3 -5
  9. package/dist/commands/constraint.js +3 -5
  10. package/dist/commands/decision.js +3 -6
  11. package/dist/commands/delete-plan.js +3 -5
  12. package/dist/commands/handoff.js +3 -5
  13. package/dist/commands/init.js +20 -0
  14. package/dist/commands/instruction.js +16 -9
  15. package/dist/commands/mcp.js +18 -22
  16. package/dist/commands/memory.js +4 -7
  17. package/dist/commands/plan.js +3 -5
  18. package/dist/commands/prune.js +27 -25
  19. package/dist/commands/reflect.js +3 -5
  20. package/dist/commands/release-claim.js +23 -20
  21. package/dist/commands/release-claims.js +22 -21
  22. package/dist/commands/rollback.js +2 -2
  23. package/dist/commands/tool.js +3 -5
  24. package/dist/commands/trap.js +3 -5
  25. package/dist/commands/update-handoff.js +3 -5
  26. package/dist/commands/update-plan.js +3 -5
  27. package/dist/commands/upgrade.js +2 -2
  28. package/dist/core/audit.js +25 -25
  29. package/dist/core/bootstrap.js +587 -4
  30. package/dist/core/candidates.js +10 -6
  31. package/dist/core/claims.js +10 -8
  32. package/dist/core/coordination.js +2 -2
  33. package/dist/core/instructions.js +4 -2
  34. package/dist/core/io.js +8 -0
  35. package/dist/core/lock.js +18 -2
  36. package/dist/core/migration.js +6 -2
  37. package/dist/core/runtime.js +18 -14
  38. package/dist/core/schema.js +69 -0
  39. package/dist/core/state.js +21 -4
  40. package/docs/cli.md +21 -1
  41. package/docs/integrations/agents.md +42 -29
  42. package/docs/integrations/claude-code.md +4 -4
  43. package/docs/integrations/codex.md +5 -5
  44. package/docs/integrations/copilot.md +3 -2
  45. package/docs/integrations/cursor.md +4 -4
  46. package/docs/integrations/mcp.md +53 -24
  47. package/docs/integrations/overview.md +53 -40
  48. package/docs/mcp-schema-changelog.md +3 -0
  49. package/docs/product/positioning.md +17 -18
  50. package/docs/quickstart.md +87 -55
  51. package/package.json +1 -2
@@ -3,27 +3,60 @@ import path from 'node:path';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { JsonStore } from './json-store.js';
5
5
  import { generateId, nowISO } from './ids.js';
6
- import { resolveEntityDir } from './io.js';
7
- import { BootstrapProfileDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
6
+ import { memoryPath, resolveEntityDir, withStoreLock, writeFileAtomic } from './io.js';
7
+ import { BootstrapApplicationReceiptSchema, BootstrapInterviewPlanSchema, BootstrapInterviewQuestionSchema, BootstrapImportPlanDocumentSchema, BootstrapProfileDocumentSchema, BootstrapSuggestionDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
8
8
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
9
9
  import { analyzeRepository } from './repo-analysis.js';
10
10
  import { buildExecutionContext, compactExecutionContext } from './execution-context.js';
11
11
  import { buildAgentToolingContext } from './agent-context.js';
12
+ import { createInstruction, loadInstructions, saveInstruction } from './instructions.js';
13
+ import { resolveCurrentAgentName } from './agent-registry.js';
14
+ import { loadState } from './state.js';
15
+ import { generateMarkdown } from './markdown.js';
12
16
  const README_CANDIDATES = ['README.md', 'README', 'README.txt', 'README.mdx'];
13
17
  const DOC_HINTS = ['docs', 'doc'];
14
18
  const MAKEFILE_NAME = 'Makefile';
15
19
  const PROFILE_FILE = 'profile.json';
20
+ const IMPORT_PLAN_FILE = 'import-plan.json';
21
+ const APPLICATION_FILE = 'last-application.json';
16
22
  const HOTSPOT_LIMIT = 3;
17
23
  const DERIVED_SCHEMA_VERSION = 2;
24
+ const EMPTY_WORKSPACE_IGNORED = new Set([
25
+ '.git',
26
+ '.brainclaw',
27
+ '.gitignore',
28
+ '.gitattributes',
29
+ '.gitmodules',
30
+ '.DS_Store',
31
+ 'Thumbs.db',
32
+ 'internal-docs',
33
+ ]);
34
+ const NATIVE_INSTRUCTION_FILES = [
35
+ 'AGENTS.md',
36
+ 'CLAUDE.md',
37
+ 'GEMINI.md',
38
+ '.windsurfrules',
39
+ '.github/copilot-instructions.md',
40
+ ];
41
+ const NATIVE_INSTRUCTION_DIRS = [
42
+ '.cursor/rules',
43
+ '.roo/rules',
44
+ '.continue/rules',
45
+ '.clinerules',
46
+ ];
18
47
  export function runBootstrapProfile(options = {}) {
19
48
  const cwd = options.cwd ?? process.cwd();
20
49
  const target = normalizeTarget(options.target);
21
50
  const existing = loadBootstrapProfile(cwd);
51
+ const existingPlan = loadBootstrapImportPlan(cwd);
52
+ const lastApplication = loadBootstrapApplication(cwd);
22
53
  const existingFingerprint = currentRepoFingerprint(cwd);
23
- if (!options.refresh && existing && isProfileReusable(existing, target, existingFingerprint)) {
54
+ if (!options.refresh && existing && existingPlan && isProfileReusable(existing, target, existingFingerprint)) {
24
55
  return {
25
56
  profile: existing,
26
57
  seeds: listBootstrapSeeds(cwd),
58
+ importPlan: existingPlan,
59
+ lastApplication,
27
60
  reusedProfile: true,
28
61
  };
29
62
  }
@@ -32,6 +65,8 @@ export function runBootstrapProfile(options = {}) {
32
65
  return {
33
66
  profile: artifacts.profile,
34
67
  seeds: artifacts.seeds,
68
+ importPlan: artifacts.importPlan,
69
+ lastApplication,
35
70
  reusedProfile: false,
36
71
  };
37
72
  }
@@ -82,8 +117,21 @@ export function selectDerivedSignals(target, maxSignals, cwd) {
82
117
  }
83
118
  export function renderBootstrapSummary(result) {
84
119
  const lines = [result.profile.summary];
120
+ if (result.profile.workspace_kind) {
121
+ lines.push(`Workspace kind: ${result.profile.workspace_kind}`);
122
+ }
123
+ if (result.profile.confidence) {
124
+ lines.push(`Confidence: ${result.profile.confidence}`);
125
+ }
85
126
  lines.push(`Sources scanned: ${result.profile.sources_scanned.join(', ') || 'none'}`);
86
127
  lines.push(`Seed count: ${result.profile.seed_count}`);
128
+ if ((result.profile.native_instruction_files?.length ?? 0) > 0) {
129
+ lines.push(`Native instruction files: ${result.profile.native_instruction_files.join(', ')}`);
130
+ }
131
+ if ((result.profile.gaps?.length ?? 0) > 0) {
132
+ lines.push(`Open gaps: ${result.profile.gaps.join(' | ')}`);
133
+ }
134
+ lines.push(`Import suggestions: ${result.importPlan.suggestion_count}`);
87
135
  if (result.profile.repo_fingerprint) {
88
136
  lines.push(`Repo fingerprint: ${result.profile.repo_fingerprint}`);
89
137
  }
@@ -93,6 +141,24 @@ export function renderBootstrapSummary(result) {
93
141
  if (result.reusedProfile) {
94
142
  lines.push('Reused existing bootstrap profile.');
95
143
  }
144
+ if (result.lastApplication && !result.lastApplication.uninstalled_at) {
145
+ lines.push(`Last bootstrap import: ${result.lastApplication.managed_artifacts.length} managed artifact(s) from ${result.lastApplication.applied_at}`);
146
+ }
147
+ if (result.importPlan.suggestions.length > 0) {
148
+ lines.push('');
149
+ lines.push('Import proposal:');
150
+ for (const suggestion of result.importPlan.suggestions.slice(0, 10)) {
151
+ const scope = suggestion.scope ? `:${suggestion.scope}` : '';
152
+ lines.push(`- [${suggestion.target}/${suggestion.confidence}] <${suggestion.layer ?? 'global'}${scope}> ${suggestion.text}`);
153
+ }
154
+ }
155
+ if ((result.importPlan.interview?.question_count ?? 0) > 0) {
156
+ lines.push('');
157
+ lines.push('Adaptive interview:');
158
+ for (const question of result.importPlan.interview.questions.slice(0, 6)) {
159
+ lines.push(`- [${question.priority}/${question.audience}] ${question.prompt}`);
160
+ }
161
+ }
96
162
  if (result.seeds.length > 0) {
97
163
  lines.push('');
98
164
  lines.push('Derived signals:');
@@ -102,9 +168,29 @@ export function renderBootstrapSummary(result) {
102
168
  }
103
169
  return lines.join('\n');
104
170
  }
171
+ export function renderBootstrapInterview(result, audience = 'any') {
172
+ const interview = result.importPlan.interview;
173
+ if (!interview || interview.question_count === 0) {
174
+ return 'No adaptive interview questions are needed right now.';
175
+ }
176
+ const questions = interview.questions.filter((question) => audience === 'any' || question.audience === 'any' || question.audience === audience);
177
+ if (questions.length === 0) {
178
+ return `No adaptive interview questions are targeted to ${audience}.`;
179
+ }
180
+ const lines = [interview.summary, `Audience: ${audience}`];
181
+ lines.push('');
182
+ for (const [index, question] of questions.entries()) {
183
+ lines.push(`${index + 1}. ${question.prompt}`);
184
+ lines.push(` Why: ${question.rationale}`);
185
+ lines.push(` Expected answer: ${question.response_kind}`);
186
+ }
187
+ return lines.join('\n');
188
+ }
105
189
  function buildBootstrapArtifacts(input) {
106
190
  const sourcesScanned = [];
107
191
  const seeds = [];
192
+ const workspace = classifyWorkspace(input.cwd);
193
+ const nativeInstructionFiles = discoverNativeInstructionFiles(input.cwd);
108
194
  const readmePath = findFirstExisting(input.cwd, README_CANDIDATES);
109
195
  if (readmePath) {
110
196
  sourcesScanned.push('README');
@@ -116,6 +202,10 @@ function buildBootstrapArtifacts(input) {
116
202
  sourcesScanned.push('AGENTS.md');
117
203
  seeds.push(...extractAgentsSeeds(agentsPath, input.target));
118
204
  }
205
+ if (nativeInstructionFiles.length > 0) {
206
+ sourcesScanned.push('native_instructions');
207
+ seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(input.cwd, relativePath)), input.cwd, input.target));
208
+ }
119
209
  const manifestResult = extractManifestSeeds(input.cwd, input.target);
120
210
  if (manifestResult.seeds.length > 0) {
121
211
  sourcesScanned.push(...manifestResult.sources);
@@ -142,12 +232,37 @@ function buildBootstrapArtifacts(input) {
142
232
  seeds.push(...gitProbe.hotspotSeeds);
143
233
  }
144
234
  const uniqueSeeds = dedupeSeeds(seeds);
235
+ const confidence = inferBootstrapConfidence({
236
+ workspaceKind: workspace.kind,
237
+ readmePresent: Boolean(readmePath),
238
+ agentsPresent,
239
+ nativeInstructionFiles,
240
+ seedCount: uniqueSeeds.length,
241
+ });
242
+ const gaps = inferBootstrapGaps({
243
+ workspaceKind: workspace.kind,
244
+ readmePresent: Boolean(readmePath),
245
+ nativeInstructionFiles,
246
+ seedCount: uniqueSeeds.length,
247
+ });
145
248
  const summary = buildSummary({
249
+ workspaceKind: workspace.kind,
146
250
  agentsPresent,
251
+ nativeInstructionFiles,
147
252
  gitAvailable: gitProbe.available,
148
253
  repoAnalysis,
149
254
  seeds: uniqueSeeds,
150
255
  target: input.target,
256
+ confidence,
257
+ gaps,
258
+ });
259
+ const importPlan = buildBootstrapImportPlan({
260
+ cwd: input.cwd,
261
+ target: input.target,
262
+ workspaceKind: workspace.kind,
263
+ confidence,
264
+ gaps,
265
+ seeds: uniqueSeeds,
151
266
  });
152
267
  return {
153
268
  profile: BootstrapProfileDocumentSchema.parse({
@@ -160,11 +275,16 @@ function buildBootstrapArtifacts(input) {
160
275
  agents_md_present: agentsPresent,
161
276
  seed_count: uniqueSeeds.length,
162
277
  target: input.target,
278
+ workspace_kind: workspace.kind,
279
+ confidence,
280
+ native_instruction_files: nativeInstructionFiles,
281
+ gaps,
163
282
  }),
164
283
  seeds: uniqueSeeds.map((seed) => MemorySeedDocumentSchema.parse({
165
284
  ...seed,
166
285
  schema_version: DERIVED_SCHEMA_VERSION,
167
286
  })),
287
+ importPlan,
168
288
  };
169
289
  }
170
290
  function extractReadmeSeeds(filepath, target) {
@@ -236,6 +356,80 @@ function extractAgentsSeeds(filepath, target) {
236
356
  }
237
357
  return seeds;
238
358
  }
359
+ export function loadBootstrapImportPlan(cwd) {
360
+ const filepath = bootstrapImportPlanPath(cwd);
361
+ if (!fs.existsSync(filepath)) {
362
+ return undefined;
363
+ }
364
+ try {
365
+ return loadVersionedJsonFile('bootstrap_import_plan', filepath).document;
366
+ }
367
+ catch {
368
+ return undefined;
369
+ }
370
+ }
371
+ export function loadBootstrapApplication(cwd) {
372
+ const filepath = bootstrapApplicationPath(cwd);
373
+ if (!fs.existsSync(filepath)) {
374
+ return undefined;
375
+ }
376
+ try {
377
+ return loadVersionedJsonFile('bootstrap_application', filepath).document;
378
+ }
379
+ catch {
380
+ return undefined;
381
+ }
382
+ }
383
+ function extractNativeInstructionSeeds(filepaths, cwd, target) {
384
+ const seeds = [];
385
+ for (const filepath of filepaths) {
386
+ if (path.basename(filepath) === 'AGENTS.md') {
387
+ continue;
388
+ }
389
+ const relativePath = path.relative(cwd, filepath).replace(/\\/g, '/');
390
+ const raw = fs.readFileSync(filepath, 'utf-8');
391
+ const lines = raw.split(/\r?\n/);
392
+ const heading = lines.find((line) => line.trim().startsWith('#'));
393
+ const firstContent = lines.find((line) => line.trim().length > 0);
394
+ const label = heading
395
+ ? heading.replace(/^#+\s*/, '').trim()
396
+ : firstContent?.trim().replace(/^[-*]\s+/, '') ?? relativePath;
397
+ seeds.push(createSeed({
398
+ text: `Native agent guidance from ${path.basename(relativePath)}: ${label}`,
399
+ seedKind: 'agent_rule',
400
+ sourceKind: 'native_instruction',
401
+ sourceRef: relativePath,
402
+ confidence: 'high',
403
+ tags: ['bootstrap', 'agent', 'native-context'],
404
+ relatedPaths: target ? [target] : undefined,
405
+ }));
406
+ let extracted = 0;
407
+ for (const line of lines) {
408
+ const trimmed = line.trim();
409
+ if (!/^([-*]|\d+\.)\s+/.test(trimmed)) {
410
+ continue;
411
+ }
412
+ const text = trimmed.replace(/^([-*]|\d+\.)\s+/, '').trim();
413
+ if (!text) {
414
+ continue;
415
+ }
416
+ seeds.push(createSeed({
417
+ text,
418
+ seedKind: 'agent_rule',
419
+ sourceKind: 'native_instruction',
420
+ sourceRef: relativePath,
421
+ confidence: 'medium',
422
+ tags: ['bootstrap', 'agent', 'native-context'],
423
+ relatedPaths: target ? [target] : undefined,
424
+ }));
425
+ extracted++;
426
+ if (extracted >= 5) {
427
+ break;
428
+ }
429
+ }
430
+ }
431
+ return seeds;
432
+ }
239
433
  function extractManifestSeeds(cwd, target) {
240
434
  const sources = [];
241
435
  const seeds = [];
@@ -468,6 +662,7 @@ function probeGit(cwd, target) {
468
662
  function persistBootstrapArtifacts(artifacts, cwd) {
469
663
  ensureBootstrapDirs(cwd);
470
664
  saveVersionedJsonFile('bootstrap_profile', bootstrapProfilePath(cwd), artifacts.profile);
665
+ saveVersionedJsonFile('bootstrap_import_plan', bootstrapImportPlanPath(cwd), artifacts.importPlan);
471
666
  const store = bootstrapSeedStore(cwd);
472
667
  const existingIds = new Set(store.list().map((seed) => seed.id));
473
668
  for (const seed of artifacts.seeds) {
@@ -502,6 +697,12 @@ function bootstrapSeedsDir(cwd) {
502
697
  function bootstrapProfilePath(cwd) {
503
698
  return path.join(bootstrapDir(cwd), PROFILE_FILE);
504
699
  }
700
+ function bootstrapImportPlanPath(cwd) {
701
+ return path.join(bootstrapDir(cwd), IMPORT_PLAN_FILE);
702
+ }
703
+ function bootstrapApplicationPath(cwd) {
704
+ return path.join(bootstrapDir(cwd), APPLICATION_FILE);
705
+ }
505
706
  function isProfileReusable(profile, target, currentFingerprint) {
506
707
  if ((profile.target ?? undefined) !== target) {
507
708
  return false;
@@ -546,11 +747,15 @@ function dedupeSeeds(seeds) {
546
747
  }
547
748
  function buildSummary(input) {
548
749
  const parts = [];
549
- parts.push(`Bootstrap summary${input.target ? ` for ${input.target}` : ''}: ${input.seeds.length} derived signal(s).`);
750
+ parts.push(`Bootstrap summary${input.target ? ` for ${input.target}` : ''}: ${input.workspaceKind} workspace, ${input.seeds.length} derived signal(s).`);
751
+ parts.push(`Confidence: ${input.confidence}.`);
550
752
  parts.push(`Repository mode looks ${input.repoAnalysis.recommendedMode}.`);
551
753
  if (input.agentsPresent) {
552
754
  parts.push('AGENTS.md detected and summarized.');
553
755
  }
756
+ if (input.nativeInstructionFiles.length > 0) {
757
+ parts.push(`${input.nativeInstructionFiles.length} native instruction file(s) detected.`);
758
+ }
554
759
  if (input.gitAvailable) {
555
760
  parts.push('Git history available for hotspot detection.');
556
761
  }
@@ -558,8 +763,386 @@ function buildSummary(input) {
558
763
  if (commandCount > 0) {
559
764
  parts.push(`${commandCount} command-oriented hint(s) found.`);
560
765
  }
766
+ if (input.gaps.length > 0) {
767
+ parts.push(`Needs follow-up on: ${input.gaps.join('; ')}.`);
768
+ }
561
769
  return parts.join(' ');
562
770
  }
771
+ function buildBootstrapImportPlan(input) {
772
+ const activeInstructionKeys = new Set(loadInstructions(input.cwd)
773
+ .filter((entry) => entry.active)
774
+ .map((entry) => instructionIdentityKey(entry.text, entry.layer, entry.scope)));
775
+ const suggestions = [];
776
+ const seenSuggestionKeys = new Set();
777
+ const importedSources = new Set();
778
+ const groupedBySource = new Map();
779
+ for (const seed of input.seeds) {
780
+ const bucket = groupedBySource.get(seed.source_ref) ?? [];
781
+ bucket.push(seed);
782
+ groupedBySource.set(seed.source_ref, bucket);
783
+ }
784
+ for (const seed of input.seeds) {
785
+ const suggestion = seedToBootstrapSuggestion(seed, false);
786
+ if (!suggestion) {
787
+ continue;
788
+ }
789
+ const suggestionKey = suggestionIdentityKey(suggestion);
790
+ if (seenSuggestionKeys.has(suggestionKey) || activeInstructionKeys.has(suggestionKey)) {
791
+ continue;
792
+ }
793
+ seenSuggestionKeys.add(suggestionKey);
794
+ importedSources.add(seed.source_ref);
795
+ suggestions.push(BootstrapSuggestionDocumentSchema.parse({
796
+ ...suggestion,
797
+ schema_version: DERIVED_SCHEMA_VERSION,
798
+ }));
799
+ }
800
+ for (const [sourceRef, seeds] of groupedBySource.entries()) {
801
+ if (importedSources.has(sourceRef)) {
802
+ continue;
803
+ }
804
+ const summarySeed = seeds.find((seed) => isBootstrapSummarySeed(seed.text));
805
+ if (!summarySeed) {
806
+ continue;
807
+ }
808
+ const suggestion = seedToBootstrapSuggestion(summarySeed, true);
809
+ if (!suggestion) {
810
+ continue;
811
+ }
812
+ const suggestionKey = suggestionIdentityKey(suggestion);
813
+ if (seenSuggestionKeys.has(suggestionKey) || activeInstructionKeys.has(suggestionKey)) {
814
+ continue;
815
+ }
816
+ seenSuggestionKeys.add(suggestionKey);
817
+ suggestions.push(BootstrapSuggestionDocumentSchema.parse({
818
+ ...suggestion,
819
+ schema_version: DERIVED_SCHEMA_VERSION,
820
+ }));
821
+ }
822
+ const summary = suggestions.length === 0
823
+ ? 'No safe bootstrap imports are ready yet; review the gaps and use an interview/import step before promoting derived context.'
824
+ : `${suggestions.length} bootstrap instruction suggestion(s) are ready to import after review.`;
825
+ const interview = buildBootstrapInterviewPlan({
826
+ workspaceKind: input.workspaceKind,
827
+ gaps: input.gaps,
828
+ confidence: input.confidence,
829
+ nativeInstructionSources: [...new Set(input.seeds
830
+ .filter((seed) => seed.source_kind === 'native_instruction')
831
+ .map((seed) => seed.source_ref))],
832
+ });
833
+ return BootstrapImportPlanDocumentSchema.parse({
834
+ schema_version: DERIVED_SCHEMA_VERSION,
835
+ derived_at: nowISO(),
836
+ target: input.target,
837
+ workspace_kind: input.workspaceKind,
838
+ confidence: input.confidence,
839
+ summary,
840
+ requires_confirmation: true,
841
+ gaps: input.gaps,
842
+ suggestion_count: suggestions.length,
843
+ suggestions,
844
+ interview,
845
+ });
846
+ }
847
+ function buildBootstrapInterviewPlan(input) {
848
+ const questions = [];
849
+ const add = (prompt, rationale, priority, audience, responseKind, gapKeys) => {
850
+ questions.push(BootstrapInterviewQuestionSchema.parse({
851
+ id: generateId('bootstrap_suggestions'),
852
+ prompt,
853
+ rationale,
854
+ priority,
855
+ audience,
856
+ response_kind: responseKind,
857
+ gap_keys: gapKeys,
858
+ }));
859
+ };
860
+ if (input.workspaceKind === 'empty') {
861
+ add('What is this workspace trying to build in one sentence?', 'An empty workspace needs a product intent anchor before Brainclaw can create durable guidance.', 'high', 'any', 'short_text', ['project intent is not documented yet']);
862
+ add('Which coding agents do you expect to use here, and should they work mostly sequentially or in parallel later?', 'Bootstrap should capture collaboration expectations early, especially before worktree isolation exists.', 'high', 'any', 'list', ['agent workflow expectations should be captured explicitly']);
863
+ add('For a CLI-only agent, what should the very first safe action be after reading context?', 'Pure CLI agents need an explicit first action because they do not benefit from rich IDE affordances.', 'medium', 'cli', 'short_text', ['agent workflow expectations should be captured explicitly']);
864
+ add('For an IDE chat agent, what should it ask or inspect before editing code?', 'IDE chat agents can ask targeted follow-up questions; capturing that expectation avoids drift between surfaces.', 'medium', 'ide_chat', 'short_text', ['agent workflow expectations should be captured explicitly']);
865
+ }
866
+ if (input.gaps.includes('no README-level project overview detected')) {
867
+ add('What is the current purpose of this existing project, and what would you want a new agent to understand first?', 'When README context is missing, Brainclaw needs an explicit project overview to avoid weak brownfield inference.', 'high', 'any', 'long_text', ['no README-level project overview detected']);
868
+ }
869
+ if (input.gaps.includes('no native agent instruction files detected')) {
870
+ add('Should Brainclaw treat this project as agent-guided even though no native agent instruction files were found?', 'This determines whether Brainclaw should synthesize workflow guidance or stay memory-only for now.', 'medium', 'any', 'boolean', ['no native agent instruction files detected']);
871
+ }
872
+ if (input.gaps.includes('derived context is sparse and may need an interview')) {
873
+ add('Which constraints or conventions are important enough that every future agent should see them immediately?', 'Sparse derived context should be turned into a small set of high-signal shared instructions or constraints.', 'high', 'any', 'list', ['derived context is sparse and may need an interview']);
874
+ }
875
+ if (input.nativeInstructionSources.length > 0) {
876
+ add(`Which parts of ${input.nativeInstructionSources.join(', ')} should become durable Brainclaw memory, and which parts should remain local agent guidance only?`, 'Native agent files are derived context first; Brainclaw needs a selective import decision instead of silently promoting them.', 'medium', 'ide_chat', 'long_text', ['native instruction import boundary']);
877
+ }
878
+ if (questions.length === 0 && input.confidence !== 'high') {
879
+ add('What is still ambiguous enough that a future agent would likely ask you before proceeding?', 'Fallback question when bootstrap confidence is not high but no specific gap generated a dedicated prompt.', 'medium', 'any', 'long_text', ['confidence fallback']);
880
+ }
881
+ const summary = questions.length === 0
882
+ ? 'No adaptive interview questions are needed right now.'
883
+ : `${questions.length} adaptive interview question(s) are ready to turn bootstrap gaps into confirmed shared memory.`;
884
+ return BootstrapInterviewPlanSchema.parse({
885
+ schema_version: DERIVED_SCHEMA_VERSION,
886
+ derived_at: nowISO(),
887
+ workspace_kind: input.workspaceKind,
888
+ audience: 'any',
889
+ summary,
890
+ question_count: questions.length,
891
+ questions,
892
+ });
893
+ }
894
+ function seedToBootstrapSuggestion(seed, allowSummaryFallback) {
895
+ if (seed.seed_kind !== 'agent_rule' && seed.seed_kind !== 'command') {
896
+ return undefined;
897
+ }
898
+ if (seed.seed_kind === 'agent_rule' && isBootstrapSummarySeed(seed.text) && !allowSummaryFallback) {
899
+ return undefined;
900
+ }
901
+ const target = inferBootstrapInstructionTarget(seed);
902
+ if (!target) {
903
+ return undefined;
904
+ }
905
+ return {
906
+ id: generateId('bootstrap_suggestions'),
907
+ target: 'instruction',
908
+ text: seed.text,
909
+ rationale: renderBootstrapSuggestionRationale(seed),
910
+ confidence: seed.confidence,
911
+ source_seed_ids: [seed.id],
912
+ source_refs: [seed.source_ref],
913
+ layer: target.layer,
914
+ scope: target.scope,
915
+ tags: normalizeBootstrapSuggestionTags(seed.tags),
916
+ related_paths: seed.related_paths,
917
+ reversible: true,
918
+ };
919
+ }
920
+ function inferBootstrapInstructionTarget(seed) {
921
+ if (seed.source_kind === 'agents_md') {
922
+ return { layer: 'global' };
923
+ }
924
+ if (seed.seed_kind === 'command') {
925
+ return { layer: 'global' };
926
+ }
927
+ if (seed.source_kind !== 'native_instruction') {
928
+ return { layer: 'global' };
929
+ }
930
+ const ref = seed.source_ref.replace(/\\/g, '/');
931
+ if (ref === 'CLAUDE.md')
932
+ return { layer: 'agent', scope: 'claude-code' };
933
+ if (ref === 'GEMINI.md')
934
+ return { layer: 'agent', scope: 'antigravity' };
935
+ if (ref === '.windsurfrules')
936
+ return { layer: 'agent', scope: 'windsurf' };
937
+ if (ref === '.github/copilot-instructions.md')
938
+ return { layer: 'agent', scope: 'github-copilot' };
939
+ if (ref.startsWith('.cursor/rules/'))
940
+ return { layer: 'agent', scope: 'cursor' };
941
+ if (ref.startsWith('.roo/rules/'))
942
+ return { layer: 'agent', scope: 'roo' };
943
+ if (ref.startsWith('.continue/rules/'))
944
+ return { layer: 'agent', scope: 'continue' };
945
+ if (ref.startsWith('.clinerules/'))
946
+ return { layer: 'agent', scope: 'cline' };
947
+ return { layer: 'global' };
948
+ }
949
+ function renderBootstrapSuggestionRationale(seed) {
950
+ switch (seed.source_kind) {
951
+ case 'agents_md':
952
+ return 'Derived from AGENTS.md';
953
+ case 'native_instruction':
954
+ return `Derived from native agent instruction file ${seed.source_ref}`;
955
+ case 'readme':
956
+ return `Derived from ${seed.source_ref}`;
957
+ case 'manifest':
958
+ return `Derived from ${seed.source_ref}`;
959
+ default:
960
+ return `Derived from ${seed.source_ref}`;
961
+ }
962
+ }
963
+ function normalizeBootstrapSuggestionTags(tags) {
964
+ const normalized = tags.filter((tag) => tag !== 'bootstrap' && tag !== 'native-context');
965
+ normalized.push('bootstrap-import');
966
+ return [...new Set(normalized)];
967
+ }
968
+ function isBootstrapSummarySeed(text) {
969
+ return text.startsWith('Agent guide: ') || text.startsWith('Native agent guidance from ');
970
+ }
971
+ function suggestionIdentityKey(suggestion) {
972
+ return instructionIdentityKey(suggestion.text, suggestion.layer ?? 'global', suggestion.scope);
973
+ }
974
+ function instructionIdentityKey(text, layer, scope) {
975
+ return `${layer}:${scope ?? '*'}:${text.trim().toLowerCase()}`;
976
+ }
977
+ export function applyBootstrapImport(options = {}) {
978
+ const cwd = options.cwd ?? process.cwd();
979
+ const result = runBootstrapProfile(options);
980
+ const proposal = result.importPlan;
981
+ if (proposal.suggestions.length === 0) {
982
+ return {
983
+ proposal,
984
+ receipt: loadBootstrapApplication(cwd),
985
+ createdCount: 0,
986
+ skippedCount: 0,
987
+ };
988
+ }
989
+ const activeInstructionKeys = new Set(loadInstructions(cwd)
990
+ .filter((entry) => entry.active)
991
+ .map((entry) => instructionIdentityKey(entry.text, entry.layer, entry.scope)));
992
+ const managedArtifacts = [];
993
+ let createdCount = 0;
994
+ let skippedCount = 0;
995
+ withStoreLock(cwd, () => {
996
+ for (const suggestion of proposal.suggestions) {
997
+ if (suggestion.target !== 'instruction') {
998
+ skippedCount++;
999
+ continue;
1000
+ }
1001
+ const identityKey = suggestionIdentityKey(suggestion);
1002
+ if (activeInstructionKeys.has(identityKey)) {
1003
+ skippedCount++;
1004
+ continue;
1005
+ }
1006
+ const entry = createInstruction(suggestion.text, {
1007
+ layer: suggestion.layer ?? 'global',
1008
+ scope: suggestion.scope,
1009
+ tags: suggestion.tags,
1010
+ author: resolveCurrentAgentName(cwd),
1011
+ }, cwd);
1012
+ activeInstructionKeys.add(identityKey);
1013
+ managedArtifacts.push({
1014
+ kind: 'instruction',
1015
+ id: entry.id,
1016
+ suggestion_id: suggestion.id,
1017
+ rollback_action: 'deactivate',
1018
+ });
1019
+ createdCount++;
1020
+ }
1021
+ if (createdCount > 0) {
1022
+ writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd)));
1023
+ }
1024
+ });
1025
+ const receipt = BootstrapApplicationReceiptSchema.parse({
1026
+ schema_version: DERIVED_SCHEMA_VERSION,
1027
+ applied_at: nowISO(),
1028
+ proposal_derived_at: proposal.derived_at,
1029
+ target: proposal.target,
1030
+ workspace_kind: proposal.workspace_kind,
1031
+ managed_artifacts: managedArtifacts,
1032
+ suggestion_ids: managedArtifacts.map((artifact) => artifact.suggestion_id),
1033
+ });
1034
+ saveVersionedJsonFile('bootstrap_application', bootstrapApplicationPath(cwd), receipt);
1035
+ return {
1036
+ proposal,
1037
+ receipt,
1038
+ createdCount,
1039
+ skippedCount,
1040
+ };
1041
+ }
1042
+ export function uninstallBootstrapImport(cwd) {
1043
+ const resolvedCwd = cwd ?? process.cwd();
1044
+ const receipt = loadBootstrapApplication(resolvedCwd);
1045
+ if (!receipt || receipt.uninstalled_at) {
1046
+ return {
1047
+ receipt,
1048
+ deactivatedCount: 0,
1049
+ skippedCount: 0,
1050
+ };
1051
+ }
1052
+ const instructions = loadInstructions(resolvedCwd);
1053
+ let deactivatedCount = 0;
1054
+ let skippedCount = 0;
1055
+ withStoreLock(resolvedCwd, () => {
1056
+ for (const artifact of receipt.managed_artifacts) {
1057
+ if (artifact.kind !== 'instruction') {
1058
+ skippedCount++;
1059
+ continue;
1060
+ }
1061
+ const instruction = instructions.find((entry) => entry.id === artifact.id);
1062
+ if (!instruction || !instruction.active) {
1063
+ skippedCount++;
1064
+ continue;
1065
+ }
1066
+ instruction.active = false;
1067
+ instruction.updated_at = nowISO();
1068
+ saveInstruction(instruction, resolvedCwd);
1069
+ deactivatedCount++;
1070
+ }
1071
+ if (deactivatedCount > 0) {
1072
+ writeFileAtomic(memoryPath('project.md', resolvedCwd), generateMarkdown(loadState(resolvedCwd)));
1073
+ }
1074
+ });
1075
+ const nextReceipt = BootstrapApplicationReceiptSchema.parse({
1076
+ ...receipt,
1077
+ uninstalled_at: nowISO(),
1078
+ });
1079
+ saveVersionedJsonFile('bootstrap_application', bootstrapApplicationPath(resolvedCwd), nextReceipt);
1080
+ return {
1081
+ receipt: nextReceipt,
1082
+ deactivatedCount,
1083
+ skippedCount,
1084
+ };
1085
+ }
1086
+ function classifyWorkspace(cwd) {
1087
+ const visibleEntries = fs.readdirSync(cwd)
1088
+ .filter((entry) => !EMPTY_WORKSPACE_IGNORED.has(entry));
1089
+ return {
1090
+ kind: visibleEntries.length === 0 ? 'empty' : 'existing',
1091
+ visibleEntries,
1092
+ };
1093
+ }
1094
+ function discoverNativeInstructionFiles(cwd) {
1095
+ const discovered = new Set();
1096
+ for (const relativePath of NATIVE_INSTRUCTION_FILES) {
1097
+ const filepath = path.join(cwd, relativePath);
1098
+ if (fs.existsSync(filepath) && fs.statSync(filepath).isFile()) {
1099
+ discovered.add(relativePath);
1100
+ }
1101
+ }
1102
+ for (const relativeDir of NATIVE_INSTRUCTION_DIRS) {
1103
+ const dir = path.join(cwd, relativeDir);
1104
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
1105
+ continue;
1106
+ }
1107
+ for (const entry of fs.readdirSync(dir).sort()) {
1108
+ if (!/\.(md|mdc)$/i.test(entry)) {
1109
+ continue;
1110
+ }
1111
+ discovered.add(path.posix.join(relativeDir.replace(/\\/g, '/'), entry));
1112
+ }
1113
+ }
1114
+ return [...discovered].sort((left, right) => left.localeCompare(right));
1115
+ }
1116
+ function inferBootstrapConfidence(input) {
1117
+ if (input.workspaceKind === 'empty') {
1118
+ return input.seedCount > 3 ? 'medium' : 'low';
1119
+ }
1120
+ if (input.readmePresent && (input.agentsPresent || input.nativeInstructionFiles.length > 0) && input.seedCount >= 6) {
1121
+ return 'high';
1122
+ }
1123
+ if (input.readmePresent || input.nativeInstructionFiles.length > 0 || input.seedCount >= 4) {
1124
+ return 'medium';
1125
+ }
1126
+ return 'low';
1127
+ }
1128
+ function inferBootstrapGaps(input) {
1129
+ const gaps = [];
1130
+ if (input.workspaceKind === 'empty') {
1131
+ gaps.push('project intent is not documented yet');
1132
+ gaps.push('agent workflow expectations should be captured explicitly');
1133
+ return gaps;
1134
+ }
1135
+ if (!input.readmePresent) {
1136
+ gaps.push('no README-level project overview detected');
1137
+ }
1138
+ if (input.nativeInstructionFiles.length === 0) {
1139
+ gaps.push('no native agent instruction files detected');
1140
+ }
1141
+ if (input.seedCount < 4) {
1142
+ gaps.push('derived context is sparse and may need an interview');
1143
+ }
1144
+ return gaps;
1145
+ }
563
1146
  function scoreSeed(seed, target) {
564
1147
  let score = seedKindWeight(seed.seed_kind) + confidenceWeight(seed.confidence);
565
1148
  if (!target) {