brainclaw 0.19.7 → 0.19.11

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.
@@ -1,37 +1,104 @@
1
+ import crypto from 'node:crypto';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { spawnSync } from 'node:child_process';
4
5
  import { JsonStore } from './json-store.js';
5
- import { generateId, nowISO } from './ids.js';
6
- import { resolveEntityDir } from './io.js';
7
- import { BootstrapProfileDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
6
+ import { generateId, generateIdWithLabel, nowISO } from './ids.js';
7
+ import { memoryPath, resolveEntityDir, withStoreLock, writeFileAtomic } from './io.js';
8
+ import { BootstrapApplicationReceiptSchema, BootstrapInterviewAnswerSchema, BootstrapInterviewPlanSchema, BootstrapInterviewQuestionSchema, BootstrapImportPlanDocumentSchema, BootstrapProfileDocumentSchema, BootstrapSuggestionDocumentSchema, MemorySeedDocumentSchema, } from './schema.js';
8
9
  import { loadVersionedJsonFile, saveVersionedJsonFile } from './migration.js';
9
10
  import { analyzeRepository } from './repo-analysis.js';
10
11
  import { buildExecutionContext, compactExecutionContext } from './execution-context.js';
11
12
  import { buildAgentToolingContext } from './agent-context.js';
13
+ import { createInstruction, loadInstructions, saveInstruction } from './instructions.js';
14
+ import { resolveCurrentAgentName } from './agent-registry.js';
15
+ import { loadState, persistState } from './state.js';
16
+ import { generateMarkdown } from './markdown.js';
12
17
  const README_CANDIDATES = ['README.md', 'README', 'README.txt', 'README.mdx'];
13
18
  const DOC_HINTS = ['docs', 'doc'];
14
19
  const MAKEFILE_NAME = 'Makefile';
15
20
  const PROFILE_FILE = 'profile.json';
21
+ const IMPORT_PLAN_FILE = 'import-plan.json';
22
+ const APPLICATION_FILE = 'last-application.json';
16
23
  const HOTSPOT_LIMIT = 3;
17
24
  const DERIVED_SCHEMA_VERSION = 2;
25
+ const EMPTY_WORKSPACE_IGNORED = new Set([
26
+ '.git',
27
+ '.brainclaw',
28
+ '.gitignore',
29
+ '.gitattributes',
30
+ '.gitmodules',
31
+ '.DS_Store',
32
+ 'Thumbs.db',
33
+ 'internal-docs',
34
+ ]);
35
+ const NATIVE_INSTRUCTION_FILES = [
36
+ 'AGENTS.md',
37
+ 'CLAUDE.md',
38
+ 'GEMINI.md',
39
+ '.windsurfrules',
40
+ '.github/copilot-instructions.md',
41
+ ];
42
+ const NATIVE_INSTRUCTION_DIRS = [
43
+ '.cursor/rules',
44
+ '.roo/rules',
45
+ '.continue/rules',
46
+ '.clinerules',
47
+ ];
18
48
  export function runBootstrapProfile(options = {}) {
19
49
  const cwd = options.cwd ?? process.cwd();
20
50
  const target = normalizeTarget(options.target);
51
+ const interviewAnswers = normalizeBootstrapInterviewAnswers(options.interviewAnswers);
21
52
  const existing = loadBootstrapProfile(cwd);
53
+ const existingPlan = loadBootstrapImportPlan(cwd);
54
+ const lastApplication = loadBootstrapApplication(cwd);
22
55
  const existingFingerprint = currentRepoFingerprint(cwd);
23
- if (!options.refresh && existing && isProfileReusable(existing, target, existingFingerprint)) {
56
+ if (!options.refresh && existing && existingPlan && isProfileReusable(existing, target, existingFingerprint)) {
57
+ const seeds = listBootstrapSeeds(cwd);
58
+ const importPlan = interviewAnswers.length > 0
59
+ ? buildBootstrapImportPlan({
60
+ cwd,
61
+ target,
62
+ workspaceKind: existing.workspace_kind ?? 'existing',
63
+ onboardingMode: existing.onboarding_mode ?? inferOnboardingMode({
64
+ workspaceKind: existing.workspace_kind ?? 'existing',
65
+ confidence: existing.confidence ?? 'low',
66
+ readmePresent: existing.sources_scanned.includes('README'),
67
+ nativeInstructionFiles: existing.native_instruction_files,
68
+ }),
69
+ confidence: existing.confidence ?? 'low',
70
+ gaps: existing.gaps,
71
+ seeds,
72
+ interviewAnswers,
73
+ })
74
+ : existingPlan;
24
75
  return {
25
76
  profile: existing,
26
- seeds: listBootstrapSeeds(cwd),
77
+ seeds,
78
+ importPlan,
79
+ lastApplication,
27
80
  reusedProfile: true,
28
81
  };
29
82
  }
30
83
  const artifacts = buildBootstrapArtifacts({ cwd, target, repoFingerprint: existingFingerprint });
31
84
  persistBootstrapArtifacts(artifacts, cwd);
85
+ const importPlan = interviewAnswers.length > 0
86
+ ? buildBootstrapImportPlan({
87
+ cwd,
88
+ target,
89
+ workspaceKind: artifacts.profile.workspace_kind ?? 'existing',
90
+ onboardingMode: artifacts.profile.onboarding_mode ?? 'existing_sparse',
91
+ confidence: artifacts.profile.confidence ?? 'low',
92
+ gaps: artifacts.profile.gaps,
93
+ seeds: artifacts.seeds,
94
+ interviewAnswers,
95
+ })
96
+ : artifacts.importPlan;
32
97
  return {
33
98
  profile: artifacts.profile,
34
99
  seeds: artifacts.seeds,
100
+ importPlan,
101
+ lastApplication,
35
102
  reusedProfile: false,
36
103
  };
37
104
  }
@@ -82,8 +149,27 @@ export function selectDerivedSignals(target, maxSignals, cwd) {
82
149
  }
83
150
  export function renderBootstrapSummary(result) {
84
151
  const lines = [result.profile.summary];
152
+ if (result.profile.workspace_kind) {
153
+ lines.push(`Workspace kind: ${result.profile.workspace_kind}`);
154
+ }
155
+ if (result.profile.onboarding_mode) {
156
+ lines.push(`Onboarding mode: ${result.profile.onboarding_mode}`);
157
+ }
158
+ if (result.profile.confidence) {
159
+ lines.push(`Confidence: ${result.profile.confidence}`);
160
+ }
85
161
  lines.push(`Sources scanned: ${result.profile.sources_scanned.join(', ') || 'none'}`);
86
162
  lines.push(`Seed count: ${result.profile.seed_count}`);
163
+ if ((result.profile.native_instruction_files?.length ?? 0) > 0) {
164
+ lines.push(`Native instruction files: ${result.profile.native_instruction_files.join(', ')}`);
165
+ }
166
+ if ((result.profile.gaps?.length ?? 0) > 0) {
167
+ lines.push(`Open gaps: ${result.profile.gaps.join(' | ')}`);
168
+ }
169
+ lines.push(`Import suggestions: ${result.importPlan.suggestion_count}`);
170
+ if ((result.importPlan.confirmed_suggestion_count ?? 0) > 0) {
171
+ lines.push(`Confirmed by interview: ${result.importPlan.confirmed_suggestion_count}`);
172
+ }
87
173
  if (result.profile.repo_fingerprint) {
88
174
  lines.push(`Repo fingerprint: ${result.profile.repo_fingerprint}`);
89
175
  }
@@ -93,6 +179,24 @@ export function renderBootstrapSummary(result) {
93
179
  if (result.reusedProfile) {
94
180
  lines.push('Reused existing bootstrap profile.');
95
181
  }
182
+ if (result.lastApplication && !result.lastApplication.uninstalled_at) {
183
+ lines.push(`Last bootstrap import: ${result.lastApplication.managed_artifacts.length} managed artifact(s) from ${result.lastApplication.applied_at}`);
184
+ }
185
+ if (result.importPlan.suggestions.length > 0) {
186
+ lines.push('');
187
+ lines.push('Import proposal:');
188
+ for (const suggestion of result.importPlan.suggestions.slice(0, 10)) {
189
+ const scope = suggestion.scope ? `:${suggestion.scope}` : '';
190
+ lines.push(`- [${suggestion.target}/${suggestion.confidence}] <${suggestion.layer ?? 'global'}${scope}> ${suggestion.text}`);
191
+ }
192
+ }
193
+ if ((result.importPlan.interview?.question_count ?? 0) > 0) {
194
+ lines.push('');
195
+ lines.push('Adaptive interview:');
196
+ for (const question of result.importPlan.interview.questions.slice(0, 6)) {
197
+ lines.push(`- [${question.priority}/${question.audience}] [${question.id}] ${question.prompt}`);
198
+ }
199
+ }
96
200
  if (result.seeds.length > 0) {
97
201
  lines.push('');
98
202
  lines.push('Derived signals:');
@@ -102,9 +206,32 @@ export function renderBootstrapSummary(result) {
102
206
  }
103
207
  return lines.join('\n');
104
208
  }
209
+ export function renderBootstrapInterview(result, audience = 'any') {
210
+ const interview = result.importPlan.interview;
211
+ if (!interview || interview.question_count === 0) {
212
+ return 'No adaptive interview questions are needed right now.';
213
+ }
214
+ const questions = interview.questions.filter((question) => audience === 'any' || question.audience === 'any' || question.audience === audience);
215
+ if (questions.length === 0) {
216
+ return `No adaptive interview questions are targeted to ${audience}.`;
217
+ }
218
+ const lines = [interview.summary, `Audience: ${audience}`];
219
+ lines.push('');
220
+ for (const [index, question] of questions.entries()) {
221
+ lines.push(`${index + 1}. [${question.id}] ${question.prompt}`);
222
+ lines.push(` Why: ${question.rationale}`);
223
+ lines.push(` Expected answer: ${question.response_kind}`);
224
+ if (question.target_hints.length > 0) {
225
+ lines.push(` Target hints: ${question.target_hints.join(', ')}`);
226
+ }
227
+ }
228
+ return lines.join('\n');
229
+ }
105
230
  function buildBootstrapArtifacts(input) {
106
231
  const sourcesScanned = [];
107
232
  const seeds = [];
233
+ const workspace = classifyWorkspace(input.cwd);
234
+ const nativeInstructionFiles = discoverNativeInstructionFiles(input.cwd);
108
235
  const readmePath = findFirstExisting(input.cwd, README_CANDIDATES);
109
236
  if (readmePath) {
110
237
  sourcesScanned.push('README');
@@ -116,6 +243,10 @@ function buildBootstrapArtifacts(input) {
116
243
  sourcesScanned.push('AGENTS.md');
117
244
  seeds.push(...extractAgentsSeeds(agentsPath, input.target));
118
245
  }
246
+ if (nativeInstructionFiles.length > 0) {
247
+ sourcesScanned.push('native_instructions');
248
+ seeds.push(...extractNativeInstructionSeeds(nativeInstructionFiles.map((relativePath) => path.join(input.cwd, relativePath)), input.cwd, input.target));
249
+ }
119
250
  const manifestResult = extractManifestSeeds(input.cwd, input.target);
120
251
  if (manifestResult.seeds.length > 0) {
121
252
  sourcesScanned.push(...manifestResult.sources);
@@ -142,12 +273,45 @@ function buildBootstrapArtifacts(input) {
142
273
  seeds.push(...gitProbe.hotspotSeeds);
143
274
  }
144
275
  const uniqueSeeds = dedupeSeeds(seeds);
276
+ const confidence = inferBootstrapConfidence({
277
+ workspaceKind: workspace.kind,
278
+ readmePresent: Boolean(readmePath),
279
+ agentsPresent,
280
+ nativeInstructionFiles,
281
+ seedCount: uniqueSeeds.length,
282
+ });
283
+ const onboardingMode = inferOnboardingMode({
284
+ workspaceKind: workspace.kind,
285
+ readmePresent: Boolean(readmePath),
286
+ nativeInstructionFiles,
287
+ confidence,
288
+ });
289
+ const gaps = inferBootstrapGaps({
290
+ workspaceKind: workspace.kind,
291
+ readmePresent: Boolean(readmePath),
292
+ nativeInstructionFiles,
293
+ seedCount: uniqueSeeds.length,
294
+ });
145
295
  const summary = buildSummary({
296
+ workspaceKind: workspace.kind,
146
297
  agentsPresent,
298
+ nativeInstructionFiles,
147
299
  gitAvailable: gitProbe.available,
148
300
  repoAnalysis,
149
301
  seeds: uniqueSeeds,
150
302
  target: input.target,
303
+ onboardingMode,
304
+ confidence,
305
+ gaps,
306
+ });
307
+ const importPlan = buildBootstrapImportPlan({
308
+ cwd: input.cwd,
309
+ target: input.target,
310
+ workspaceKind: workspace.kind,
311
+ onboardingMode,
312
+ confidence,
313
+ gaps,
314
+ seeds: uniqueSeeds,
151
315
  });
152
316
  return {
153
317
  profile: BootstrapProfileDocumentSchema.parse({
@@ -160,11 +324,17 @@ function buildBootstrapArtifacts(input) {
160
324
  agents_md_present: agentsPresent,
161
325
  seed_count: uniqueSeeds.length,
162
326
  target: input.target,
327
+ workspace_kind: workspace.kind,
328
+ onboarding_mode: onboardingMode,
329
+ confidence,
330
+ native_instruction_files: nativeInstructionFiles,
331
+ gaps,
163
332
  }),
164
333
  seeds: uniqueSeeds.map((seed) => MemorySeedDocumentSchema.parse({
165
334
  ...seed,
166
335
  schema_version: DERIVED_SCHEMA_VERSION,
167
336
  })),
337
+ importPlan,
168
338
  };
169
339
  }
170
340
  function extractReadmeSeeds(filepath, target) {
@@ -236,6 +406,80 @@ function extractAgentsSeeds(filepath, target) {
236
406
  }
237
407
  return seeds;
238
408
  }
409
+ export function loadBootstrapImportPlan(cwd) {
410
+ const filepath = bootstrapImportPlanPath(cwd);
411
+ if (!fs.existsSync(filepath)) {
412
+ return undefined;
413
+ }
414
+ try {
415
+ return loadVersionedJsonFile('bootstrap_import_plan', filepath).document;
416
+ }
417
+ catch {
418
+ return undefined;
419
+ }
420
+ }
421
+ export function loadBootstrapApplication(cwd) {
422
+ const filepath = bootstrapApplicationPath(cwd);
423
+ if (!fs.existsSync(filepath)) {
424
+ return undefined;
425
+ }
426
+ try {
427
+ return loadVersionedJsonFile('bootstrap_application', filepath).document;
428
+ }
429
+ catch {
430
+ return undefined;
431
+ }
432
+ }
433
+ function extractNativeInstructionSeeds(filepaths, cwd, target) {
434
+ const seeds = [];
435
+ for (const filepath of filepaths) {
436
+ if (path.basename(filepath) === 'AGENTS.md') {
437
+ continue;
438
+ }
439
+ const relativePath = path.relative(cwd, filepath).replace(/\\/g, '/');
440
+ const raw = fs.readFileSync(filepath, 'utf-8');
441
+ const lines = raw.split(/\r?\n/);
442
+ const heading = lines.find((line) => line.trim().startsWith('#'));
443
+ const firstContent = lines.find((line) => line.trim().length > 0);
444
+ const label = heading
445
+ ? heading.replace(/^#+\s*/, '').trim()
446
+ : firstContent?.trim().replace(/^[-*]\s+/, '') ?? relativePath;
447
+ seeds.push(createSeed({
448
+ text: `Native agent guidance from ${path.basename(relativePath)}: ${label}`,
449
+ seedKind: 'agent_rule',
450
+ sourceKind: 'native_instruction',
451
+ sourceRef: relativePath,
452
+ confidence: 'high',
453
+ tags: ['bootstrap', 'agent', 'native-context'],
454
+ relatedPaths: target ? [target] : undefined,
455
+ }));
456
+ let extracted = 0;
457
+ for (const line of lines) {
458
+ const trimmed = line.trim();
459
+ if (!/^([-*]|\d+\.)\s+/.test(trimmed)) {
460
+ continue;
461
+ }
462
+ const text = trimmed.replace(/^([-*]|\d+\.)\s+/, '').trim();
463
+ if (!text) {
464
+ continue;
465
+ }
466
+ seeds.push(createSeed({
467
+ text,
468
+ seedKind: 'agent_rule',
469
+ sourceKind: 'native_instruction',
470
+ sourceRef: relativePath,
471
+ confidence: 'medium',
472
+ tags: ['bootstrap', 'agent', 'native-context'],
473
+ relatedPaths: target ? [target] : undefined,
474
+ }));
475
+ extracted++;
476
+ if (extracted >= 5) {
477
+ break;
478
+ }
479
+ }
480
+ }
481
+ return seeds;
482
+ }
239
483
  function extractManifestSeeds(cwd, target) {
240
484
  const sources = [];
241
485
  const seeds = [];
@@ -468,6 +712,7 @@ function probeGit(cwd, target) {
468
712
  function persistBootstrapArtifacts(artifacts, cwd) {
469
713
  ensureBootstrapDirs(cwd);
470
714
  saveVersionedJsonFile('bootstrap_profile', bootstrapProfilePath(cwd), artifacts.profile);
715
+ saveVersionedJsonFile('bootstrap_import_plan', bootstrapImportPlanPath(cwd), artifacts.importPlan);
471
716
  const store = bootstrapSeedStore(cwd);
472
717
  const existingIds = new Set(store.list().map((seed) => seed.id));
473
718
  for (const seed of artifacts.seeds) {
@@ -502,6 +747,12 @@ function bootstrapSeedsDir(cwd) {
502
747
  function bootstrapProfilePath(cwd) {
503
748
  return path.join(bootstrapDir(cwd), PROFILE_FILE);
504
749
  }
750
+ function bootstrapImportPlanPath(cwd) {
751
+ return path.join(bootstrapDir(cwd), IMPORT_PLAN_FILE);
752
+ }
753
+ function bootstrapApplicationPath(cwd) {
754
+ return path.join(bootstrapDir(cwd), APPLICATION_FILE);
755
+ }
505
756
  function isProfileReusable(profile, target, currentFingerprint) {
506
757
  if ((profile.target ?? undefined) !== target) {
507
758
  return false;
@@ -546,11 +797,16 @@ function dedupeSeeds(seeds) {
546
797
  }
547
798
  function buildSummary(input) {
548
799
  const parts = [];
549
- parts.push(`Bootstrap summary${input.target ? ` for ${input.target}` : ''}: ${input.seeds.length} derived signal(s).`);
800
+ parts.push(`Bootstrap summary${input.target ? ` for ${input.target}` : ''}: ${input.workspaceKind} workspace, ${input.seeds.length} derived signal(s).`);
801
+ parts.push(`Onboarding mode: ${input.onboardingMode}.`);
802
+ parts.push(`Confidence: ${input.confidence}.`);
550
803
  parts.push(`Repository mode looks ${input.repoAnalysis.recommendedMode}.`);
551
804
  if (input.agentsPresent) {
552
805
  parts.push('AGENTS.md detected and summarized.');
553
806
  }
807
+ if (input.nativeInstructionFiles.length > 0) {
808
+ parts.push(`${input.nativeInstructionFiles.length} native instruction file(s) detected.`);
809
+ }
554
810
  if (input.gitAvailable) {
555
811
  parts.push('Git history available for hotspot detection.');
556
812
  }
@@ -558,8 +814,691 @@ function buildSummary(input) {
558
814
  if (commandCount > 0) {
559
815
  parts.push(`${commandCount} command-oriented hint(s) found.`);
560
816
  }
817
+ if (input.gaps.length > 0) {
818
+ parts.push(`Needs follow-up on: ${input.gaps.join('; ')}.`);
819
+ }
561
820
  return parts.join(' ');
562
821
  }
822
+ function buildBootstrapImportPlan(input) {
823
+ const activeInstructionKeys = new Set(loadInstructions(input.cwd)
824
+ .filter((entry) => entry.active)
825
+ .map((entry) => instructionIdentityKey(entry.text, entry.layer, entry.scope)));
826
+ const suggestions = [];
827
+ const seenSuggestionKeys = new Set();
828
+ const importedSources = new Set();
829
+ const groupedBySource = new Map();
830
+ for (const seed of input.seeds) {
831
+ const bucket = groupedBySource.get(seed.source_ref) ?? [];
832
+ bucket.push(seed);
833
+ groupedBySource.set(seed.source_ref, bucket);
834
+ }
835
+ for (const seed of input.seeds) {
836
+ const suggestion = seedToBootstrapSuggestion(seed, false);
837
+ if (!suggestion) {
838
+ continue;
839
+ }
840
+ const suggestionKey = genericSuggestionIdentityKey(suggestion);
841
+ if (seenSuggestionKeys.has(suggestionKey) || activeInstructionKeys.has(suggestionKey)) {
842
+ continue;
843
+ }
844
+ seenSuggestionKeys.add(suggestionKey);
845
+ importedSources.add(seed.source_ref);
846
+ suggestions.push(BootstrapSuggestionDocumentSchema.parse({
847
+ ...suggestion,
848
+ schema_version: DERIVED_SCHEMA_VERSION,
849
+ }));
850
+ }
851
+ for (const [sourceRef, seeds] of groupedBySource.entries()) {
852
+ if (importedSources.has(sourceRef)) {
853
+ continue;
854
+ }
855
+ const summarySeed = seeds.find((seed) => isBootstrapSummarySeed(seed.text));
856
+ if (!summarySeed) {
857
+ continue;
858
+ }
859
+ const suggestion = seedToBootstrapSuggestion(summarySeed, true);
860
+ if (!suggestion) {
861
+ continue;
862
+ }
863
+ const suggestionKey = genericSuggestionIdentityKey(suggestion);
864
+ if (seenSuggestionKeys.has(suggestionKey) || activeInstructionKeys.has(suggestionKey)) {
865
+ continue;
866
+ }
867
+ seenSuggestionKeys.add(suggestionKey);
868
+ suggestions.push(BootstrapSuggestionDocumentSchema.parse({
869
+ ...suggestion,
870
+ schema_version: DERIVED_SCHEMA_VERSION,
871
+ }));
872
+ }
873
+ const interview = buildBootstrapInterviewPlan({
874
+ workspaceKind: input.workspaceKind,
875
+ gaps: input.gaps,
876
+ confidence: input.confidence,
877
+ nativeInstructionSources: [...new Set(input.seeds
878
+ .filter((seed) => seed.source_kind === 'native_instruction')
879
+ .map((seed) => seed.source_ref))],
880
+ });
881
+ const interviewSuggestions = buildBootstrapInterviewSuggestions(interview, input.interviewAnswers ?? []);
882
+ let confirmedSuggestionCount = 0;
883
+ for (const suggestion of interviewSuggestions) {
884
+ const suggestionKey = genericSuggestionIdentityKey(suggestion);
885
+ if ((suggestion.target === 'instruction' && activeInstructionKeys.has(suggestionKey)) || seenSuggestionKeys.has(suggestionKey)) {
886
+ continue;
887
+ }
888
+ seenSuggestionKeys.add(suggestionKey);
889
+ confirmedSuggestionCount++;
890
+ suggestions.push(BootstrapSuggestionDocumentSchema.parse({
891
+ ...suggestion,
892
+ schema_version: DERIVED_SCHEMA_VERSION,
893
+ }));
894
+ }
895
+ const summary = suggestions.length === 0
896
+ ? 'No safe bootstrap imports are ready yet; review the gaps and use an interview/import step before promoting derived context.'
897
+ : `${suggestions.length} bootstrap suggestion(s) are ready to import after review${confirmedSuggestionCount > 0 ? `, including ${confirmedSuggestionCount} confirmed via interview` : ''}.`;
898
+ return BootstrapImportPlanDocumentSchema.parse({
899
+ schema_version: DERIVED_SCHEMA_VERSION,
900
+ derived_at: nowISO(),
901
+ target: input.target,
902
+ workspace_kind: input.workspaceKind,
903
+ onboarding_mode: input.onboardingMode,
904
+ confidence: input.confidence,
905
+ summary,
906
+ requires_confirmation: true,
907
+ gaps: input.gaps,
908
+ confirmed_suggestion_count: confirmedSuggestionCount,
909
+ interview_answer_count: input.interviewAnswers?.length ?? 0,
910
+ suggestion_count: suggestions.length,
911
+ suggestions,
912
+ interview,
913
+ });
914
+ }
915
+ function buildBootstrapInterviewPlan(input) {
916
+ const questions = [];
917
+ const add = (prompt, rationale, priority, audience, responseKind, gapKeys, targetHints) => {
918
+ questions.push(BootstrapInterviewQuestionSchema.parse({
919
+ id: bootstrapInterviewQuestionId(prompt, audience),
920
+ prompt,
921
+ rationale,
922
+ priority,
923
+ audience,
924
+ response_kind: responseKind,
925
+ gap_keys: gapKeys,
926
+ target_hints: targetHints,
927
+ }));
928
+ };
929
+ if (input.workspaceKind === 'empty') {
930
+ 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'], ['decision']);
931
+ 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'], ['constraint', 'instruction']);
932
+ 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'], ['instruction']);
933
+ 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'], ['instruction']);
934
+ }
935
+ if (input.gaps.includes('no README-level project overview detected')) {
936
+ 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'], ['decision']);
937
+ }
938
+ if (input.gaps.includes('no native agent instruction files detected')) {
939
+ 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'], ['decision']);
940
+ }
941
+ if (input.gaps.includes('derived context is sparse and may need an interview')) {
942
+ 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'], ['constraint']);
943
+ }
944
+ if (input.nativeInstructionSources.length > 0) {
945
+ 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'], ['decision', 'instruction', 'constraint']);
946
+ }
947
+ if (questions.length === 0 && input.confidence !== 'high') {
948
+ 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'], ['instruction', 'constraint']);
949
+ }
950
+ const summary = questions.length === 0
951
+ ? 'No adaptive interview questions are needed right now.'
952
+ : `${questions.length} adaptive interview question(s) are ready to turn bootstrap gaps into confirmed shared memory.`;
953
+ return BootstrapInterviewPlanSchema.parse({
954
+ schema_version: DERIVED_SCHEMA_VERSION,
955
+ derived_at: nowISO(),
956
+ workspace_kind: input.workspaceKind,
957
+ audience: 'any',
958
+ summary,
959
+ question_count: questions.length,
960
+ questions,
961
+ });
962
+ }
963
+ function bootstrapInterviewQuestionId(prompt, audience) {
964
+ const digest = crypto.createHash('sha1').update(`${audience}:${prompt}`).digest('hex').slice(0, 8);
965
+ return `biq_${digest}`;
966
+ }
967
+ function normalizeBootstrapInterviewAnswers(answers) {
968
+ if (!answers || answers.length === 0) {
969
+ return [];
970
+ }
971
+ return answers.map((answer) => BootstrapInterviewAnswerSchema.parse(answer));
972
+ }
973
+ function buildBootstrapInterviewSuggestions(interview, answers) {
974
+ if (!interview || answers.length === 0) {
975
+ return [];
976
+ }
977
+ const questionsById = new Map(interview.questions.map((question) => [question.id, question]));
978
+ const suggestions = [];
979
+ for (const answer of answers) {
980
+ const question = questionsById.get(answer.question_id);
981
+ if (!question) {
982
+ continue;
983
+ }
984
+ if (answer.suggestions.length > 0) {
985
+ for (const suggestion of answer.suggestions) {
986
+ suggestions.push({
987
+ id: generateId('bootstrap_suggestions'),
988
+ target: suggestion.target,
989
+ text: suggestion.text.trim(),
990
+ rationale: suggestion.rationale ?? renderBootstrapInterviewRationale(question),
991
+ confidence: suggestion.confidence ?? 'high',
992
+ source_seed_ids: [],
993
+ source_refs: [`bootstrap-interview:${question.id}`],
994
+ layer: suggestion.layer,
995
+ scope: suggestion.scope,
996
+ tags: normalizeBootstrapSuggestionTags(['bootstrap-interview', ...suggestion.tags]),
997
+ related_paths: suggestion.related_paths,
998
+ category: suggestion.category,
999
+ outcome: suggestion.outcome,
1000
+ severity: suggestion.severity,
1001
+ reversible: true,
1002
+ });
1003
+ }
1004
+ continue;
1005
+ }
1006
+ suggestions.push(...deriveBootstrapSuggestionsFromAnswer(question, answer));
1007
+ }
1008
+ return suggestions.filter((suggestion) => suggestion.text.trim().length > 0);
1009
+ }
1010
+ function deriveBootstrapSuggestionsFromAnswer(question, answer) {
1011
+ const itemTexts = answer.response_items.map((entry) => entry.trim()).filter(Boolean);
1012
+ const text = answer.response_text?.trim();
1013
+ const base = {
1014
+ id: generateId('bootstrap_suggestions'),
1015
+ rationale: renderBootstrapInterviewRationale(question),
1016
+ confidence: 'high',
1017
+ source_seed_ids: [],
1018
+ source_refs: [`bootstrap-interview:${question.id}`],
1019
+ tags: normalizeBootstrapSuggestionTags(['bootstrap-interview']),
1020
+ related_paths: undefined,
1021
+ reversible: true,
1022
+ };
1023
+ if (question.gap_keys.includes('project intent is not documented yet') && text) {
1024
+ return [{ ...base, target: 'decision', text: `Project intent: ${text}` }];
1025
+ }
1026
+ if (question.gap_keys.includes('no README-level project overview detected') && text) {
1027
+ return [{ ...base, target: 'decision', text: `Project overview: ${text}` }];
1028
+ }
1029
+ if (question.prompt.startsWith('Which coding agents do you expect to use here') && (itemTexts.length > 0 || text)) {
1030
+ const values = itemTexts.length > 0 ? itemTexts : [text];
1031
+ return values.map((entry) => ({
1032
+ ...base,
1033
+ id: generateId('bootstrap_suggestions'),
1034
+ target: 'constraint',
1035
+ text: `Agent workflow expectation: ${entry}`,
1036
+ category: 'process',
1037
+ }));
1038
+ }
1039
+ if (question.prompt.startsWith('For a CLI-only agent') && text) {
1040
+ return [{ ...base, target: 'instruction', text: `CLI-only agents should first: ${text}`, layer: 'global' }];
1041
+ }
1042
+ if (question.prompt.startsWith('For an IDE chat agent') && text) {
1043
+ return [{ ...base, target: 'instruction', text: `IDE chat agents should ask or inspect: ${text}`, layer: 'global' }];
1044
+ }
1045
+ if (question.gap_keys.includes('no native agent instruction files detected') && answer.response_boolean !== undefined) {
1046
+ return [{
1047
+ ...base,
1048
+ target: 'decision',
1049
+ text: answer.response_boolean
1050
+ ? 'Brainclaw should treat this project as agent-guided.'
1051
+ : 'Brainclaw should stay memory-only until agent guidance is clarified.',
1052
+ }];
1053
+ }
1054
+ if (question.gap_keys.includes('derived context is sparse and may need an interview') && (itemTexts.length > 0 || text)) {
1055
+ const values = itemTexts.length > 0 ? itemTexts : [text];
1056
+ return values.map((entry) => ({
1057
+ ...base,
1058
+ id: generateId('bootstrap_suggestions'),
1059
+ target: 'constraint',
1060
+ text: entry,
1061
+ category: 'process',
1062
+ }));
1063
+ }
1064
+ if (question.gap_keys.includes('native instruction import boundary') && text) {
1065
+ return [{ ...base, target: 'decision', text: `Native instruction import boundary: ${text}` }];
1066
+ }
1067
+ if (question.gap_keys.includes('confidence fallback') && text) {
1068
+ return [{
1069
+ ...base,
1070
+ target: question.target_hints.includes('constraint') ? 'constraint' : 'instruction',
1071
+ text,
1072
+ category: question.target_hints.includes('constraint') ? 'process' : undefined,
1073
+ layer: question.target_hints.includes('instruction') ? 'global' : undefined,
1074
+ }];
1075
+ }
1076
+ if (itemTexts.length > 0 && question.target_hints.includes('constraint')) {
1077
+ return itemTexts.map((entry) => ({
1078
+ ...base,
1079
+ id: generateId('bootstrap_suggestions'),
1080
+ target: 'constraint',
1081
+ text: entry,
1082
+ category: 'process',
1083
+ }));
1084
+ }
1085
+ if (text && question.target_hints.includes('instruction')) {
1086
+ return [{ ...base, target: 'instruction', text, layer: 'global' }];
1087
+ }
1088
+ if (text && question.target_hints.includes('decision')) {
1089
+ return [{ ...base, target: 'decision', text }];
1090
+ }
1091
+ return [];
1092
+ }
1093
+ function renderBootstrapInterviewRationale(question) {
1094
+ return `Confirmed via bootstrap interview answer to ${question.id}: ${question.prompt}`;
1095
+ }
1096
+ function seedToBootstrapSuggestion(seed, allowSummaryFallback) {
1097
+ if (seed.seed_kind !== 'agent_rule' && seed.seed_kind !== 'command') {
1098
+ return undefined;
1099
+ }
1100
+ if (seed.seed_kind === 'agent_rule' && isBootstrapSummarySeed(seed.text) && !allowSummaryFallback) {
1101
+ return undefined;
1102
+ }
1103
+ const target = inferBootstrapInstructionTarget(seed);
1104
+ if (!target) {
1105
+ return undefined;
1106
+ }
1107
+ return {
1108
+ id: generateId('bootstrap_suggestions'),
1109
+ target: 'instruction',
1110
+ text: seed.text,
1111
+ rationale: renderBootstrapSuggestionRationale(seed),
1112
+ confidence: seed.confidence,
1113
+ source_seed_ids: [seed.id],
1114
+ source_refs: [seed.source_ref],
1115
+ layer: target.layer,
1116
+ scope: target.scope,
1117
+ tags: normalizeBootstrapSuggestionTags(seed.tags),
1118
+ related_paths: seed.related_paths,
1119
+ reversible: true,
1120
+ };
1121
+ }
1122
+ function inferBootstrapInstructionTarget(seed) {
1123
+ if (seed.source_kind === 'agents_md') {
1124
+ return { layer: 'global' };
1125
+ }
1126
+ if (seed.seed_kind === 'command') {
1127
+ return { layer: 'global' };
1128
+ }
1129
+ if (seed.source_kind !== 'native_instruction') {
1130
+ return { layer: 'global' };
1131
+ }
1132
+ const ref = seed.source_ref.replace(/\\/g, '/');
1133
+ if (ref === 'CLAUDE.md')
1134
+ return { layer: 'agent', scope: 'claude-code' };
1135
+ if (ref === 'GEMINI.md')
1136
+ return { layer: 'agent', scope: 'antigravity' };
1137
+ if (ref === '.windsurfrules')
1138
+ return { layer: 'agent', scope: 'windsurf' };
1139
+ if (ref === '.github/copilot-instructions.md')
1140
+ return { layer: 'agent', scope: 'github-copilot' };
1141
+ if (ref.startsWith('.cursor/rules/'))
1142
+ return { layer: 'agent', scope: 'cursor' };
1143
+ if (ref.startsWith('.roo/rules/'))
1144
+ return { layer: 'agent', scope: 'roo' };
1145
+ if (ref.startsWith('.continue/rules/'))
1146
+ return { layer: 'agent', scope: 'continue' };
1147
+ if (ref.startsWith('.clinerules/'))
1148
+ return { layer: 'agent', scope: 'cline' };
1149
+ return { layer: 'global' };
1150
+ }
1151
+ function renderBootstrapSuggestionRationale(seed) {
1152
+ switch (seed.source_kind) {
1153
+ case 'agents_md':
1154
+ return 'Derived from AGENTS.md';
1155
+ case 'native_instruction':
1156
+ return `Derived from native agent instruction file ${seed.source_ref}`;
1157
+ case 'readme':
1158
+ return `Derived from ${seed.source_ref}`;
1159
+ case 'manifest':
1160
+ return `Derived from ${seed.source_ref}`;
1161
+ default:
1162
+ return `Derived from ${seed.source_ref}`;
1163
+ }
1164
+ }
1165
+ function normalizeBootstrapSuggestionTags(tags) {
1166
+ const normalized = tags.filter((tag) => tag !== 'bootstrap' && tag !== 'native-context');
1167
+ normalized.push('bootstrap-import');
1168
+ return [...new Set(normalized)];
1169
+ }
1170
+ function isBootstrapSummarySeed(text) {
1171
+ return text.startsWith('Agent guide: ') || text.startsWith('Native agent guidance from ');
1172
+ }
1173
+ function genericSuggestionIdentityKey(suggestion) {
1174
+ if (suggestion.target === 'instruction') {
1175
+ return instructionIdentityKey(suggestion.text, suggestion.layer ?? 'global', suggestion.scope);
1176
+ }
1177
+ if (suggestion.target === 'trap') {
1178
+ return `trap:${suggestion.severity ?? 'medium'}:${suggestion.text.trim().toLowerCase()}`;
1179
+ }
1180
+ return `${suggestion.target}:${suggestion.text.trim().toLowerCase()}`;
1181
+ }
1182
+ function instructionIdentityKey(text, layer, scope) {
1183
+ return `${layer}:${scope ?? '*'}:${text.trim().toLowerCase()}`;
1184
+ }
1185
+ export function applyBootstrapImport(options = {}) {
1186
+ const cwd = options.cwd ?? process.cwd();
1187
+ const result = runBootstrapProfile(options);
1188
+ const proposal = result.importPlan;
1189
+ if (proposal.suggestions.length === 0) {
1190
+ return {
1191
+ proposal,
1192
+ receipt: loadBootstrapApplication(cwd),
1193
+ createdCount: 0,
1194
+ skippedCount: 0,
1195
+ };
1196
+ }
1197
+ const managedArtifacts = [];
1198
+ let createdCount = 0;
1199
+ let skippedCount = 0;
1200
+ withStoreLock(cwd, () => {
1201
+ const state = loadState(cwd);
1202
+ const activeInstructionKeys = new Set(loadInstructions(cwd)
1203
+ .filter((entry) => entry.active)
1204
+ .map((entry) => instructionIdentityKey(entry.text, entry.layer, entry.scope)));
1205
+ const activeDecisionKeys = new Set(state.recent_decisions.map((entry) => genericSuggestionIdentityKey({
1206
+ target: 'decision',
1207
+ text: entry.text,
1208
+ layer: 'global',
1209
+ scope: undefined,
1210
+ severity: undefined,
1211
+ })));
1212
+ const activeConstraintKeys = new Set(state.active_constraints
1213
+ .filter((entry) => entry.status === 'active')
1214
+ .map((entry) => genericSuggestionIdentityKey({
1215
+ target: 'constraint',
1216
+ text: entry.text,
1217
+ layer: 'global',
1218
+ scope: undefined,
1219
+ severity: undefined,
1220
+ })));
1221
+ const activeTrapKeys = new Set(state.known_traps
1222
+ .filter((entry) => entry.status === 'active')
1223
+ .map((entry) => genericSuggestionIdentityKey({
1224
+ target: 'trap',
1225
+ text: entry.text,
1226
+ layer: 'global',
1227
+ scope: undefined,
1228
+ severity: entry.severity,
1229
+ })));
1230
+ let stateChanged = false;
1231
+ for (const suggestion of proposal.suggestions) {
1232
+ const identityKey = genericSuggestionIdentityKey(suggestion);
1233
+ if ((suggestion.target === 'instruction' && activeInstructionKeys.has(identityKey)) ||
1234
+ (suggestion.target === 'decision' && activeDecisionKeys.has(identityKey)) ||
1235
+ (suggestion.target === 'constraint' && activeConstraintKeys.has(identityKey)) ||
1236
+ (suggestion.target === 'trap' && activeTrapKeys.has(identityKey))) {
1237
+ skippedCount++;
1238
+ continue;
1239
+ }
1240
+ switch (suggestion.target) {
1241
+ case 'instruction': {
1242
+ const entry = createInstruction(suggestion.text, {
1243
+ layer: suggestion.layer ?? 'global',
1244
+ scope: suggestion.scope,
1245
+ tags: suggestion.tags,
1246
+ author: resolveCurrentAgentName(cwd),
1247
+ }, cwd);
1248
+ activeInstructionKeys.add(identityKey);
1249
+ managedArtifacts.push({
1250
+ kind: 'instruction',
1251
+ id: entry.id,
1252
+ suggestion_id: suggestion.id,
1253
+ rollback_action: 'deactivate',
1254
+ });
1255
+ createdCount++;
1256
+ break;
1257
+ }
1258
+ case 'decision': {
1259
+ const { id, short_label } = generateIdWithLabel('recent_decisions', cwd);
1260
+ const entry = {
1261
+ id,
1262
+ short_label,
1263
+ text: suggestion.text,
1264
+ created_at: nowISO(),
1265
+ author: resolveCurrentAgentName(cwd),
1266
+ outcome: suggestion.outcome,
1267
+ tags: suggestion.tags ?? [],
1268
+ related_paths: suggestion.related_paths,
1269
+ };
1270
+ state.recent_decisions.push(entry);
1271
+ activeDecisionKeys.add(identityKey);
1272
+ managedArtifacts.push({
1273
+ kind: 'decision',
1274
+ id: entry.id,
1275
+ suggestion_id: suggestion.id,
1276
+ rollback_action: 'delete',
1277
+ });
1278
+ stateChanged = true;
1279
+ createdCount++;
1280
+ break;
1281
+ }
1282
+ case 'constraint': {
1283
+ const { id, short_label } = generateIdWithLabel('active_constraints', cwd);
1284
+ const entry = {
1285
+ id,
1286
+ short_label,
1287
+ text: suggestion.text,
1288
+ created_at: nowISO(),
1289
+ author: resolveCurrentAgentName(cwd),
1290
+ status: 'active',
1291
+ category: suggestion.category,
1292
+ tags: suggestion.tags ?? [],
1293
+ related_paths: suggestion.related_paths,
1294
+ };
1295
+ state.active_constraints.push(entry);
1296
+ activeConstraintKeys.add(identityKey);
1297
+ managedArtifacts.push({
1298
+ kind: 'constraint',
1299
+ id: entry.id,
1300
+ suggestion_id: suggestion.id,
1301
+ rollback_action: 'delete',
1302
+ });
1303
+ stateChanged = true;
1304
+ createdCount++;
1305
+ break;
1306
+ }
1307
+ case 'trap': {
1308
+ const { id, short_label } = generateIdWithLabel('known_traps', cwd);
1309
+ const entry = {
1310
+ id,
1311
+ short_label,
1312
+ text: suggestion.text,
1313
+ created_at: nowISO(),
1314
+ author: resolveCurrentAgentName(cwd),
1315
+ status: 'active',
1316
+ severity: suggestion.severity ?? 'medium',
1317
+ tags: suggestion.tags ?? [],
1318
+ related_paths: suggestion.related_paths,
1319
+ visibility: 'shared',
1320
+ };
1321
+ state.known_traps.push(entry);
1322
+ activeTrapKeys.add(identityKey);
1323
+ managedArtifacts.push({
1324
+ kind: 'trap',
1325
+ id: entry.id,
1326
+ suggestion_id: suggestion.id,
1327
+ rollback_action: 'delete',
1328
+ });
1329
+ stateChanged = true;
1330
+ createdCount++;
1331
+ break;
1332
+ }
1333
+ }
1334
+ }
1335
+ if (stateChanged) {
1336
+ persistState(state, cwd, { writeProjectMarkdown: false });
1337
+ }
1338
+ if (createdCount > 0) {
1339
+ writeFileAtomic(memoryPath('project.md', cwd), generateMarkdown(loadState(cwd), cwd));
1340
+ }
1341
+ });
1342
+ const receipt = BootstrapApplicationReceiptSchema.parse({
1343
+ schema_version: DERIVED_SCHEMA_VERSION,
1344
+ applied_at: nowISO(),
1345
+ proposal_derived_at: proposal.derived_at,
1346
+ target: proposal.target,
1347
+ workspace_kind: proposal.workspace_kind,
1348
+ managed_artifacts: managedArtifacts,
1349
+ suggestion_ids: managedArtifacts.map((artifact) => artifact.suggestion_id),
1350
+ });
1351
+ saveVersionedJsonFile('bootstrap_application', bootstrapApplicationPath(cwd), receipt);
1352
+ return {
1353
+ proposal,
1354
+ receipt,
1355
+ createdCount,
1356
+ skippedCount,
1357
+ };
1358
+ }
1359
+ export function uninstallBootstrapImport(cwd) {
1360
+ const resolvedCwd = cwd ?? process.cwd();
1361
+ const receipt = loadBootstrapApplication(resolvedCwd);
1362
+ if (!receipt || receipt.uninstalled_at) {
1363
+ return {
1364
+ receipt,
1365
+ deactivatedCount: 0,
1366
+ deletedCount: 0,
1367
+ skippedCount: 0,
1368
+ };
1369
+ }
1370
+ let deactivatedCount = 0;
1371
+ let deletedCount = 0;
1372
+ let skippedCount = 0;
1373
+ withStoreLock(resolvedCwd, () => {
1374
+ const state = loadState(resolvedCwd);
1375
+ const instructions = loadInstructions(resolvedCwd);
1376
+ let stateChanged = false;
1377
+ for (const artifact of receipt.managed_artifacts) {
1378
+ if (artifact.kind === 'instruction') {
1379
+ const instruction = instructions.find((entry) => entry.id === artifact.id);
1380
+ if (!instruction || !instruction.active) {
1381
+ skippedCount++;
1382
+ continue;
1383
+ }
1384
+ instruction.active = false;
1385
+ instruction.updated_at = nowISO();
1386
+ saveInstruction(instruction, resolvedCwd);
1387
+ deactivatedCount++;
1388
+ continue;
1389
+ }
1390
+ const beforeCounts = {
1391
+ decisions: state.recent_decisions.length,
1392
+ constraints: state.active_constraints.length,
1393
+ traps: state.known_traps.length,
1394
+ };
1395
+ if (artifact.kind === 'decision') {
1396
+ state.recent_decisions = state.recent_decisions.filter((entry) => entry.id !== artifact.id);
1397
+ }
1398
+ else if (artifact.kind === 'constraint') {
1399
+ state.active_constraints = state.active_constraints.filter((entry) => entry.id !== artifact.id);
1400
+ }
1401
+ else if (artifact.kind === 'trap') {
1402
+ state.known_traps = state.known_traps.filter((entry) => entry.id !== artifact.id);
1403
+ }
1404
+ const changed = beforeCounts.decisions !== state.recent_decisions.length
1405
+ || beforeCounts.constraints !== state.active_constraints.length
1406
+ || beforeCounts.traps !== state.known_traps.length;
1407
+ if (!changed) {
1408
+ skippedCount++;
1409
+ continue;
1410
+ }
1411
+ stateChanged = true;
1412
+ deletedCount++;
1413
+ }
1414
+ if (stateChanged) {
1415
+ persistState(state, resolvedCwd, { writeProjectMarkdown: false });
1416
+ }
1417
+ if (deactivatedCount > 0 || deletedCount > 0) {
1418
+ writeFileAtomic(memoryPath('project.md', resolvedCwd), generateMarkdown(loadState(resolvedCwd), resolvedCwd));
1419
+ }
1420
+ });
1421
+ const nextReceipt = BootstrapApplicationReceiptSchema.parse({
1422
+ ...receipt,
1423
+ uninstalled_at: nowISO(),
1424
+ });
1425
+ saveVersionedJsonFile('bootstrap_application', bootstrapApplicationPath(resolvedCwd), nextReceipt);
1426
+ return {
1427
+ receipt: nextReceipt,
1428
+ deactivatedCount,
1429
+ deletedCount,
1430
+ skippedCount,
1431
+ };
1432
+ }
1433
+ function classifyWorkspace(cwd) {
1434
+ const visibleEntries = fs.readdirSync(cwd)
1435
+ .filter((entry) => !EMPTY_WORKSPACE_IGNORED.has(entry));
1436
+ return {
1437
+ kind: visibleEntries.length === 0 ? 'empty' : 'existing',
1438
+ visibleEntries,
1439
+ };
1440
+ }
1441
+ function discoverNativeInstructionFiles(cwd) {
1442
+ const discovered = new Set();
1443
+ for (const relativePath of NATIVE_INSTRUCTION_FILES) {
1444
+ const filepath = path.join(cwd, relativePath);
1445
+ if (fs.existsSync(filepath) && fs.statSync(filepath).isFile()) {
1446
+ discovered.add(relativePath);
1447
+ }
1448
+ }
1449
+ for (const relativeDir of NATIVE_INSTRUCTION_DIRS) {
1450
+ const dir = path.join(cwd, relativeDir);
1451
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
1452
+ continue;
1453
+ }
1454
+ for (const entry of fs.readdirSync(dir).sort()) {
1455
+ if (!/\.(md|mdc)$/i.test(entry)) {
1456
+ continue;
1457
+ }
1458
+ discovered.add(path.posix.join(relativeDir.replace(/\\/g, '/'), entry));
1459
+ }
1460
+ }
1461
+ return [...discovered].sort((left, right) => left.localeCompare(right));
1462
+ }
1463
+ function inferBootstrapConfidence(input) {
1464
+ if (input.workspaceKind === 'empty') {
1465
+ return input.seedCount > 3 ? 'medium' : 'low';
1466
+ }
1467
+ if (input.readmePresent && (input.agentsPresent || input.nativeInstructionFiles.length > 0) && input.seedCount >= 6) {
1468
+ return 'high';
1469
+ }
1470
+ if (input.readmePresent || input.nativeInstructionFiles.length > 0 || input.seedCount >= 4) {
1471
+ return 'medium';
1472
+ }
1473
+ return 'low';
1474
+ }
1475
+ function inferOnboardingMode(input) {
1476
+ if (input.workspaceKind === 'empty') {
1477
+ return 'empty_workspace';
1478
+ }
1479
+ if (input.readmePresent || input.nativeInstructionFiles.length > 0 || input.confidence === 'high') {
1480
+ return 'existing_documented';
1481
+ }
1482
+ return 'existing_sparse';
1483
+ }
1484
+ function inferBootstrapGaps(input) {
1485
+ const gaps = [];
1486
+ if (input.workspaceKind === 'empty') {
1487
+ gaps.push('project intent is not documented yet');
1488
+ gaps.push('agent workflow expectations should be captured explicitly');
1489
+ return gaps;
1490
+ }
1491
+ if (!input.readmePresent) {
1492
+ gaps.push('no README-level project overview detected');
1493
+ }
1494
+ if (input.nativeInstructionFiles.length === 0) {
1495
+ gaps.push('no native agent instruction files detected');
1496
+ }
1497
+ if (input.seedCount < 4) {
1498
+ gaps.push('derived context is sparse and may need an interview');
1499
+ }
1500
+ return gaps;
1501
+ }
563
1502
  function scoreSeed(seed, target) {
564
1503
  let score = seedKindWeight(seed.seed_kind) + confidenceWeight(seed.confidence);
565
1504
  if (!target) {