@qlucent/fishi-core 0.1.0 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -441,10 +441,18 @@ declare function getAgentFactoryTemplate(): string;
441
441
  */
442
442
  declare function getCoordinatorFactoryTemplate(): string;
443
443
 
444
+ type ConflictResolution = 'skip' | 'merge' | 'replace';
445
+ interface FileResolutionMap {
446
+ categories: Record<string, ConflictResolution>;
447
+ files: Record<string, ConflictResolution>;
448
+ }
444
449
  interface ScaffoldOptions extends InitOptions {
445
450
  projectName: string;
446
451
  projectType: ProjectType;
447
452
  brownfieldAnalysis?: BrownfieldAnalysisData;
453
+ resolutions?: FileResolutionMap;
454
+ docsReadmeExists?: boolean;
455
+ rootClaudeMdExists?: boolean;
448
456
  }
449
457
  interface ScaffoldResult {
450
458
  agentCount: number;
@@ -455,4 +463,64 @@ interface ScaffoldResult {
455
463
  }
456
464
  declare function generateScaffold(targetDir: string, options: ScaffoldOptions): Promise<ScaffoldResult>;
457
465
 
458
- export { type AgentDefinition, type AgentRole, type AgentTemplate, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type CostMode, type DetectionCheck, type DetectionResult, type DynamicAgentConfig, type ExecutionConfig, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type ProjectConfig, type ProjectType, type ProjectYamlOptions, type ScaffoldOptions, type ScaffoldResult, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, frontendAgentTemplate, fullstackAgentTemplate, generateScaffold, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getApiDesignSkill, getAutoCheckpointHook, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDebuggingSkill, getDeploymentSkill, getDocCheckerScript, getDocumentationSkill, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getModelRoutingReference, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSafetyCheckHook, getSessionStartHook, getSettingsJsonTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, researchAgentTemplate, securityAgentTemplate, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
466
+ interface FileConflict {
467
+ path: string;
468
+ size: number;
469
+ }
470
+ interface ConflictCategory {
471
+ name: string;
472
+ label: string;
473
+ conflicts: FileConflict[];
474
+ }
475
+ interface ConflictMap {
476
+ categories: ConflictCategory[];
477
+ hasConflicts: boolean;
478
+ totalConflicts: number;
479
+ docsReadmeExists: boolean;
480
+ rootClaudeMdExists: boolean;
481
+ }
482
+ declare function detectConflicts(targetDir: string): ConflictMap;
483
+
484
+ interface BackupManifest {
485
+ timestamp: string;
486
+ fishi_version: string;
487
+ files: {
488
+ path: string;
489
+ size: number;
490
+ }[];
491
+ }
492
+ declare function createBackup(targetDir: string, conflictingFiles: string[]): Promise<string>;
493
+
494
+ /**
495
+ * Prepend FISHI content to the TOP of an existing CLAUDE.md (for root CLAUDE.md).
496
+ * FISHI rules go first so they have higher priority in Claude Code.
497
+ */
498
+ declare function mergeClaudeMdTop(existing: string, fishiContent: string): string;
499
+ /**
500
+ * Append FISHI content to the BOTTOM of an existing CLAUDE.md (for .claude/CLAUDE.md).
501
+ */
502
+ declare function mergeClaudeMd(existing: string, fishiContent: string): string;
503
+ interface HookEntry {
504
+ matcher: string;
505
+ command: string;
506
+ [key: string]: unknown;
507
+ }
508
+ interface SettingsJson {
509
+ hooks?: Record<string, HookEntry[]>;
510
+ permissions?: {
511
+ allow?: string[];
512
+ deny?: string[];
513
+ };
514
+ env?: Record<string, string>;
515
+ enabledPlugins?: string[];
516
+ [key: string]: unknown;
517
+ }
518
+ declare function mergeSettingsJson(existing: SettingsJson, fishi: SettingsJson): SettingsJson;
519
+ interface McpJson {
520
+ mcpServers: Record<string, Record<string, unknown>>;
521
+ [key: string]: unknown;
522
+ }
523
+ declare function mergeMcpJson(existing: McpJson, fishi: McpJson): McpJson;
524
+ declare function mergeGitignore(existing: string, fishiAdditions: string): string;
525
+
526
+ export { type AgentDefinition, type AgentRole, type AgentTemplate, type BackupManifest, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type ConflictCategory, type ConflictMap, type ConflictResolution, type CostMode, type DetectionCheck, type DetectionResult, type DynamicAgentConfig, type ExecutionConfig, type FileConflict, type FileResolutionMap, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type ProjectConfig, type ProjectType, type ProjectYamlOptions, type ScaffoldOptions, type ScaffoldResult, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, createBackup, detectConflicts, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, frontendAgentTemplate, fullstackAgentTemplate, generateScaffold, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getApiDesignSkill, getAutoCheckpointHook, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDebuggingSkill, getDeploymentSkill, getDocCheckerScript, getDocumentationSkill, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getModelRoutingReference, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSafetyCheckHook, getSessionStartHook, getSettingsJsonTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, mergeClaudeMd, mergeClaudeMdTop, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, researchAgentTemplate, securityAgentTemplate, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
package/dist/index.js CHANGED
@@ -10375,12 +10375,117 @@ RECOMMENDATION: suggested next step
10375
10375
  }
10376
10376
 
10377
10377
  // src/generators/scaffold.ts
10378
- import { mkdir, writeFile, readFile, appendFile } from "fs/promises";
10378
+ import { mkdir, writeFile, readFile } from "fs/promises";
10379
+ import { readFile as fsReadFile } from "fs/promises";
10379
10380
  import { existsSync } from "fs";
10380
10381
  import { join, dirname } from "path";
10382
+
10383
+ // src/generators/merge-strategies.ts
10384
+ var FISHI_START = "<!-- FISHI:START \u2014 Auto-generated by FISHI. Do not edit between markers. -->";
10385
+ var FISHI_END = "<!-- FISHI:END -->";
10386
+ function mergeClaudeMdTop(existing, fishiContent) {
10387
+ const section = `${FISHI_START}
10388
+ ### FISHI Framework
10389
+ ${fishiContent}
10390
+ ${FISHI_END}
10391
+ ---
10392
+ `;
10393
+ const startIdx = existing.indexOf(FISHI_START);
10394
+ const endIdx = existing.indexOf(FISHI_END);
10395
+ if (startIdx !== -1 && endIdx !== -1) {
10396
+ const sectionEnd = endIdx + FISHI_END.length;
10397
+ let afterEnd = sectionEnd;
10398
+ const afterContent = existing.slice(afterEnd);
10399
+ const separatorMatch = afterContent.match(/^\n?---\n/);
10400
+ if (separatorMatch) {
10401
+ afterEnd += separatorMatch[0].length;
10402
+ }
10403
+ return section + existing.slice(afterEnd);
10404
+ }
10405
+ return section + "\n" + existing;
10406
+ }
10407
+ function mergeClaudeMd(existing, fishiContent) {
10408
+ const section = `
10409
+ ---
10410
+ ### FISHI Framework
10411
+ ${FISHI_START}
10412
+ ${fishiContent}
10413
+ ${FISHI_END}`;
10414
+ const startIdx = existing.indexOf(FISHI_START);
10415
+ const endIdx = existing.indexOf(FISHI_END);
10416
+ if (startIdx !== -1 && endIdx !== -1) {
10417
+ let sectionStart = existing.lastIndexOf("\n---\n### FISHI Framework", startIdx);
10418
+ if (sectionStart === -1) sectionStart = startIdx;
10419
+ const sectionEnd = endIdx + FISHI_END.length;
10420
+ return existing.slice(0, sectionStart) + section + existing.slice(sectionEnd);
10421
+ }
10422
+ return existing + section;
10423
+ }
10424
+ function mergeSettingsJson(existing, fishi) {
10425
+ const result = { ...existing };
10426
+ const mergedHooks = { ...existing.hooks };
10427
+ if (fishi.hooks) {
10428
+ for (const [event, entries] of Object.entries(fishi.hooks)) {
10429
+ if (!mergedHooks[event]) {
10430
+ mergedHooks[event] = [...entries];
10431
+ } else {
10432
+ for (const entry of entries) {
10433
+ const isDuplicate = mergedHooks[event].some(
10434
+ (e) => e.matcher === entry.matcher && e.command === entry.command
10435
+ );
10436
+ if (!isDuplicate) {
10437
+ mergedHooks[event] = [...mergedHooks[event], entry];
10438
+ }
10439
+ }
10440
+ }
10441
+ }
10442
+ }
10443
+ result.hooks = mergedHooks;
10444
+ const existingAllow = existing.permissions?.allow || [];
10445
+ const fishiAllow = fishi.permissions?.allow || [];
10446
+ const existingDeny = existing.permissions?.deny || [];
10447
+ const fishiDeny = fishi.permissions?.deny || [];
10448
+ result.permissions = {
10449
+ allow: [.../* @__PURE__ */ new Set([...existingAllow, ...fishiAllow])],
10450
+ deny: [.../* @__PURE__ */ new Set([...existingDeny, ...fishiDeny])]
10451
+ };
10452
+ if (fishi.env) {
10453
+ result.env = { ...fishi.env, ...existing.env };
10454
+ }
10455
+ if (fishi.enabledPlugins) {
10456
+ result.enabledPlugins = [
10457
+ .../* @__PURE__ */ new Set([...existing.enabledPlugins || [], ...fishi.enabledPlugins])
10458
+ ];
10459
+ }
10460
+ return result;
10461
+ }
10462
+ function mergeMcpJson(existing, fishi) {
10463
+ const merged = { ...existing, mcpServers: { ...existing.mcpServers } };
10464
+ for (const [name, config] of Object.entries(fishi.mcpServers)) {
10465
+ if (!(name in merged.mcpServers)) {
10466
+ merged.mcpServers[name] = config;
10467
+ }
10468
+ }
10469
+ return merged;
10470
+ }
10471
+ function mergeGitignore(existing, fishiAdditions) {
10472
+ if (existing.includes(".trees/")) {
10473
+ return existing;
10474
+ }
10475
+ return existing + fishiAdditions;
10476
+ }
10477
+
10478
+ // src/generators/scaffold.ts
10381
10479
  async function generateScaffold(targetDir, options) {
10382
10480
  let filesCreated = 0;
10383
- async function write(relativePath, content) {
10481
+ const resolutions = options.resolutions;
10482
+ async function write(relativePath, content, category) {
10483
+ if (resolutions) {
10484
+ const fileRes = resolutions.files[relativePath];
10485
+ const catRes = category ? resolutions.categories[category] : void 0;
10486
+ const resolution = fileRes || catRes;
10487
+ if (resolution === "skip") return;
10488
+ }
10384
10489
  const fullPath = join(targetDir, relativePath);
10385
10490
  await mkdir(dirname(fullPath), { recursive: true });
10386
10491
  await writeFile(fullPath, content, "utf-8");
@@ -10431,39 +10536,39 @@ async function generateScaffold(targetDir, options) {
10431
10536
  for (const dir of dirs) {
10432
10537
  await mkdir(join(targetDir, dir), { recursive: true });
10433
10538
  }
10434
- await write(".claude/agents/master-orchestrator.md", getMasterOrchestratorTemplate());
10435
- await write(".claude/agents/coordinators/planning-lead.md", planningLeadTemplate(ctx));
10436
- await write(".claude/agents/coordinators/dev-lead.md", devLeadTemplate(ctx));
10437
- await write(".claude/agents/coordinators/quality-lead.md", qualityLeadTemplate(ctx));
10438
- await write(".claude/agents/coordinators/ops-lead.md", opsLeadTemplate(ctx));
10439
- await write(".claude/agents/research-agent.md", researchAgentTemplate(ctx));
10440
- await write(".claude/agents/planning-agent.md", planningAgentTemplate(ctx));
10441
- await write(".claude/agents/architect-agent.md", architectAgentTemplate(ctx));
10442
- await write(".claude/agents/backend-agent.md", backendAgentTemplate(ctx));
10443
- await write(".claude/agents/frontend-agent.md", frontendAgentTemplate(ctx));
10444
- await write(".claude/agents/uiux-agent.md", uiuxAgentTemplate(ctx));
10445
- await write(".claude/agents/fullstack-agent.md", fullstackAgentTemplate(ctx));
10446
- await write(".claude/agents/devops-agent.md", devopsAgentTemplate(ctx));
10447
- await write(".claude/agents/testing-agent.md", testingAgentTemplate(ctx));
10448
- await write(".claude/agents/security-agent.md", securityAgentTemplate(ctx));
10449
- await write(".claude/agents/docs-agent.md", docsAgentTemplate(ctx));
10450
- await write(".claude/agents/writing-agent.md", writingAgentTemplate(ctx));
10451
- await write(".claude/agents/marketing-agent.md", marketingAgentTemplate(ctx));
10539
+ await write(".claude/agents/master-orchestrator.md", getMasterOrchestratorTemplate(), "agents");
10540
+ await write(".claude/agents/coordinators/planning-lead.md", planningLeadTemplate(ctx), "agents");
10541
+ await write(".claude/agents/coordinators/dev-lead.md", devLeadTemplate(ctx), "agents");
10542
+ await write(".claude/agents/coordinators/quality-lead.md", qualityLeadTemplate(ctx), "agents");
10543
+ await write(".claude/agents/coordinators/ops-lead.md", opsLeadTemplate(ctx), "agents");
10544
+ await write(".claude/agents/research-agent.md", researchAgentTemplate(ctx), "agents");
10545
+ await write(".claude/agents/planning-agent.md", planningAgentTemplate(ctx), "agents");
10546
+ await write(".claude/agents/architect-agent.md", architectAgentTemplate(ctx), "agents");
10547
+ await write(".claude/agents/backend-agent.md", backendAgentTemplate(ctx), "agents");
10548
+ await write(".claude/agents/frontend-agent.md", frontendAgentTemplate(ctx), "agents");
10549
+ await write(".claude/agents/uiux-agent.md", uiuxAgentTemplate(ctx), "agents");
10550
+ await write(".claude/agents/fullstack-agent.md", fullstackAgentTemplate(ctx), "agents");
10551
+ await write(".claude/agents/devops-agent.md", devopsAgentTemplate(ctx), "agents");
10552
+ await write(".claude/agents/testing-agent.md", testingAgentTemplate(ctx), "agents");
10553
+ await write(".claude/agents/security-agent.md", securityAgentTemplate(ctx), "agents");
10554
+ await write(".claude/agents/docs-agent.md", docsAgentTemplate(ctx), "agents");
10555
+ await write(".claude/agents/writing-agent.md", writingAgentTemplate(ctx), "agents");
10556
+ await write(".claude/agents/marketing-agent.md", marketingAgentTemplate(ctx), "agents");
10452
10557
  const agentCount = 18;
10453
10558
  await write(".fishi/agent-factory/agent-template.md", getAgentFactoryTemplate());
10454
10559
  await write(".fishi/agent-factory/coordinator-template.md", getCoordinatorFactoryTemplate());
10455
- await write(".claude/skills/brainstorming/SKILL.md", getBrainstormingSkill());
10456
- await write(".claude/skills/brownfield-analysis/SKILL.md", getBrownfieldAnalysisSkill());
10457
- await write(".claude/skills/taskboard-ops/SKILL.md", getTaskboardOpsSkill());
10458
- await write(".claude/skills/code-gen/SKILL.md", getCodeGenSkill());
10459
- await write(".claude/skills/debugging/SKILL.md", getDebuggingSkill());
10460
- await write(".claude/skills/api-design/SKILL.md", getApiDesignSkill());
10461
- await write(".claude/skills/testing/SKILL.md", getTestingSkill());
10462
- await write(".claude/skills/deployment/SKILL.md", getDeploymentSkill());
10463
- await write(".claude/skills/prd/SKILL.md", getPrdSkill());
10464
- await write(".claude/skills/brownfield-discovery/SKILL.md", getBrownfieldDiscoverySkill());
10465
- await write(".claude/skills/adaptive-taskgraph/SKILL.md", getAdaptiveTaskGraphSkill());
10466
- await write(".claude/skills/documentation/SKILL.md", getDocumentationSkill());
10560
+ await write(".claude/skills/brainstorming/SKILL.md", getBrainstormingSkill(), "skills");
10561
+ await write(".claude/skills/brownfield-analysis/SKILL.md", getBrownfieldAnalysisSkill(), "skills");
10562
+ await write(".claude/skills/taskboard-ops/SKILL.md", getTaskboardOpsSkill(), "skills");
10563
+ await write(".claude/skills/code-gen/SKILL.md", getCodeGenSkill(), "skills");
10564
+ await write(".claude/skills/debugging/SKILL.md", getDebuggingSkill(), "skills");
10565
+ await write(".claude/skills/api-design/SKILL.md", getApiDesignSkill(), "skills");
10566
+ await write(".claude/skills/testing/SKILL.md", getTestingSkill(), "skills");
10567
+ await write(".claude/skills/deployment/SKILL.md", getDeploymentSkill(), "skills");
10568
+ await write(".claude/skills/prd/SKILL.md", getPrdSkill(), "skills");
10569
+ await write(".claude/skills/brownfield-discovery/SKILL.md", getBrownfieldDiscoverySkill(), "skills");
10570
+ await write(".claude/skills/adaptive-taskgraph/SKILL.md", getAdaptiveTaskGraphSkill(), "skills");
10571
+ await write(".claude/skills/documentation/SKILL.md", getDocumentationSkill(), "skills");
10467
10572
  const skillCount = 12;
10468
10573
  await write(".fishi/scripts/session-start.mjs", getSessionStartHook());
10469
10574
  await write(".fishi/scripts/auto-checkpoint.mjs", getAutoCheckpointHook());
@@ -10496,14 +10601,14 @@ async function generateScaffold(targetDir, options) {
10496
10601
  await write(".fishi/todos/agents/frontend-agent.md", todoTemplate("frontend-agent"));
10497
10602
  await write(".fishi/todos/agents/testing-agent.md", todoTemplate("testing-agent"));
10498
10603
  await write(".fishi/todos/agents/devops-agent.md", todoTemplate("devops-agent"));
10499
- await write(".claude/commands/fishi-init.md", getInitCommand());
10500
- await write(".claude/commands/fishi-status.md", getStatusCommand());
10501
- await write(".claude/commands/fishi-resume.md", getResumeCommand());
10502
- await write(".claude/commands/fishi-gate.md", getGateCommand());
10503
- await write(".claude/commands/fishi-board.md", getBoardCommand());
10504
- await write(".claude/commands/fishi-sprint.md", getSprintCommand());
10505
- await write(".claude/commands/fishi-reset.md", getResetCommand());
10506
- await write(".claude/commands/fishi-prd.md", getPrdCommand());
10604
+ await write(".claude/commands/fishi-init.md", getInitCommand(), "commands");
10605
+ await write(".claude/commands/fishi-status.md", getStatusCommand(), "commands");
10606
+ await write(".claude/commands/fishi-resume.md", getResumeCommand(), "commands");
10607
+ await write(".claude/commands/fishi-gate.md", getGateCommand(), "commands");
10608
+ await write(".claude/commands/fishi-board.md", getBoardCommand(), "commands");
10609
+ await write(".claude/commands/fishi-sprint.md", getSprintCommand(), "commands");
10610
+ await write(".claude/commands/fishi-reset.md", getResetCommand(), "commands");
10611
+ await write(".claude/commands/fishi-prd.md", getPrdCommand(), "commands");
10507
10612
  const commandCount = 8;
10508
10613
  await write(".fishi/fishi.yaml", getFishiYamlTemplate({
10509
10614
  projectName: options.projectName,
@@ -10513,16 +10618,50 @@ async function generateScaffold(targetDir, options) {
10513
10618
  language: options.language,
10514
10619
  framework: options.framework
10515
10620
  }));
10516
- await write(".claude/settings.json", getSettingsJsonTemplate());
10517
- await write(".claude/CLAUDE.md", getClaudeMdTemplate({
10621
+ const settingsContent = getSettingsJsonTemplate();
10622
+ if (resolutions?.categories["settings-json"] === "merge") {
10623
+ const existingRaw = await fsReadFile(join(targetDir, ".claude", "settings.json"), "utf-8");
10624
+ const merged = mergeSettingsJson(JSON.parse(existingRaw), JSON.parse(settingsContent));
10625
+ await write(".claude/settings.json", JSON.stringify(merged, null, 2) + "\n", "settings-json");
10626
+ } else {
10627
+ await write(".claude/settings.json", settingsContent, "settings-json");
10628
+ }
10629
+ const claudeMdContent = getClaudeMdTemplate({
10518
10630
  projectName: options.projectName,
10519
10631
  projectDescription: ctx.projectDescription,
10520
10632
  projectType: options.projectType,
10521
10633
  language: options.language,
10522
10634
  framework: options.framework,
10523
10635
  brownfieldAnalysis: options.brownfieldAnalysis
10524
- }));
10525
- await write(".mcp.json", getMcpJsonTemplate());
10636
+ });
10637
+ if (options.rootClaudeMdExists) {
10638
+ const rootResolution = resolutions?.categories["root-claude-md"];
10639
+ if (rootResolution === "merge") {
10640
+ const existingMd = await fsReadFile(join(targetDir, "CLAUDE.md"), "utf-8");
10641
+ const merged = mergeClaudeMdTop(existingMd, claudeMdContent);
10642
+ const fullPath = join(targetDir, "CLAUDE.md");
10643
+ await writeFile(fullPath, merged, "utf-8");
10644
+ filesCreated++;
10645
+ } else if (rootResolution === "replace") {
10646
+ const fullPath = join(targetDir, "CLAUDE.md");
10647
+ await writeFile(fullPath, claudeMdContent, "utf-8");
10648
+ filesCreated++;
10649
+ }
10650
+ } else if (resolutions?.categories["claude-md"] === "merge") {
10651
+ const existingMd = await fsReadFile(join(targetDir, ".claude", "CLAUDE.md"), "utf-8");
10652
+ const merged = mergeClaudeMd(existingMd, claudeMdContent);
10653
+ await write(".claude/CLAUDE.md", merged, "claude-md");
10654
+ } else {
10655
+ await write(".claude/CLAUDE.md", claudeMdContent, "claude-md");
10656
+ }
10657
+ const mcpContent = getMcpJsonTemplate();
10658
+ if (resolutions?.categories["mcp-json"] === "merge") {
10659
+ const existingRaw = await fsReadFile(join(targetDir, ".mcp.json"), "utf-8");
10660
+ const merged = mergeMcpJson(JSON.parse(existingRaw), JSON.parse(mcpContent));
10661
+ await write(".mcp.json", JSON.stringify(merged, null, 2) + "\n", "mcp-json");
10662
+ } else {
10663
+ await write(".mcp.json", mcpContent, "mcp-json");
10664
+ }
10526
10665
  await write(".fishi/state/project.yaml", getProjectYamlTemplate({
10527
10666
  projectName: options.projectName,
10528
10667
  projectDescription: ctx.projectDescription,
@@ -10537,18 +10676,24 @@ async function generateScaffold(targetDir, options) {
10537
10676
  await write(".fishi/memory/decisions.md", "# Architecture Decision Log\n\n_No decisions recorded yet._\n");
10538
10677
  await write(".fishi/memory/agents/master-orchestrator.md", getMasterOrchestratorMemory(ctx));
10539
10678
  await write(".fishi/learnings/shared.md", "# Learnings \u2014 shared\n\n## Mistakes & Fixes\n\n## Best Practices\n");
10540
- await write("docs/README.md", `# ${options.projectName}
10679
+ if (!options.docsReadmeExists) {
10680
+ await write("docs/README.md", `# ${options.projectName}
10541
10681
 
10542
10682
  Documentation will be generated as the project progresses.
10543
10683
  `);
10684
+ }
10544
10685
  await write(".fishi/taskboard/board.md", getEmptyBoard());
10545
10686
  await write(".fishi/taskboard/backlog.md", getEmptyBacklog());
10546
10687
  const gitignorePath = join(targetDir, ".gitignore");
10547
10688
  const additions = getGitignoreAdditions();
10548
10689
  if (existsSync(gitignorePath)) {
10549
- const existing = await readFile(gitignorePath, "utf-8");
10550
- if (!existing.includes(".trees/")) {
10551
- await appendFile(gitignorePath, "\n" + additions, "utf-8");
10690
+ if (resolutions?.categories["gitignore"] === "skip") {
10691
+ } else {
10692
+ const existing = await readFile(gitignorePath, "utf-8");
10693
+ const merged = mergeGitignore(existing, "\n" + additions);
10694
+ if (merged !== existing) {
10695
+ await writeFile(join(targetDir, ".gitignore"), merged, "utf-8");
10696
+ }
10552
10697
  }
10553
10698
  } else {
10554
10699
  await writeFile(gitignorePath, additions, "utf-8");
@@ -10648,9 +10793,157 @@ Prioritized list of all upcoming work items.
10648
10793
  _Backlog is empty. Use /fishi-init to start the planning process._
10649
10794
  `;
10650
10795
  }
10796
+
10797
+ // src/generators/conflict-detector.ts
10798
+ import { existsSync as existsSync2, statSync } from "fs";
10799
+ import { join as join2 } from "path";
10800
+ var FISHI_FILES = {
10801
+ "claude-md": {
10802
+ label: ".claude/CLAUDE.md",
10803
+ files: [".claude/CLAUDE.md"]
10804
+ },
10805
+ "settings-json": {
10806
+ label: ".claude/settings.json",
10807
+ files: [".claude/settings.json"]
10808
+ },
10809
+ "mcp-json": {
10810
+ label: ".mcp.json",
10811
+ files: [".mcp.json"]
10812
+ },
10813
+ "agents": {
10814
+ label: "Agents",
10815
+ files: [
10816
+ ".claude/agents/master-orchestrator.md",
10817
+ ".claude/agents/coordinators/planning-lead.md",
10818
+ ".claude/agents/coordinators/dev-lead.md",
10819
+ ".claude/agents/coordinators/quality-lead.md",
10820
+ ".claude/agents/coordinators/ops-lead.md",
10821
+ ".claude/agents/research-agent.md",
10822
+ ".claude/agents/planning-agent.md",
10823
+ ".claude/agents/architect-agent.md",
10824
+ ".claude/agents/backend-agent.md",
10825
+ ".claude/agents/frontend-agent.md",
10826
+ ".claude/agents/uiux-agent.md",
10827
+ ".claude/agents/fullstack-agent.md",
10828
+ ".claude/agents/devops-agent.md",
10829
+ ".claude/agents/testing-agent.md",
10830
+ ".claude/agents/security-agent.md",
10831
+ ".claude/agents/docs-agent.md",
10832
+ ".claude/agents/writing-agent.md",
10833
+ ".claude/agents/marketing-agent.md"
10834
+ ]
10835
+ },
10836
+ "skills": {
10837
+ label: "Skills",
10838
+ files: [
10839
+ ".claude/skills/brainstorming/SKILL.md",
10840
+ ".claude/skills/brownfield-analysis/SKILL.md",
10841
+ ".claude/skills/taskboard-ops/SKILL.md",
10842
+ ".claude/skills/code-gen/SKILL.md",
10843
+ ".claude/skills/debugging/SKILL.md",
10844
+ ".claude/skills/api-design/SKILL.md",
10845
+ ".claude/skills/testing/SKILL.md",
10846
+ ".claude/skills/deployment/SKILL.md",
10847
+ ".claude/skills/prd/SKILL.md",
10848
+ ".claude/skills/brownfield-discovery/SKILL.md",
10849
+ ".claude/skills/adaptive-taskgraph/SKILL.md",
10850
+ ".claude/skills/documentation/SKILL.md"
10851
+ ]
10852
+ },
10853
+ "commands": {
10854
+ label: "Commands",
10855
+ files: [
10856
+ ".claude/commands/fishi-init.md",
10857
+ ".claude/commands/fishi-status.md",
10858
+ ".claude/commands/fishi-resume.md",
10859
+ ".claude/commands/fishi-gate.md",
10860
+ ".claude/commands/fishi-board.md",
10861
+ ".claude/commands/fishi-sprint.md",
10862
+ ".claude/commands/fishi-reset.md",
10863
+ ".claude/commands/fishi-prd.md"
10864
+ ]
10865
+ },
10866
+ "gitignore": {
10867
+ label: ".gitignore",
10868
+ files: [".gitignore"]
10869
+ }
10870
+ };
10871
+ function detectConflicts(targetDir) {
10872
+ const categories = [];
10873
+ let totalConflicts = 0;
10874
+ const rootClaudeMdExists = existsSync2(join2(targetDir, "CLAUDE.md"));
10875
+ for (const [name, def] of Object.entries(FISHI_FILES)) {
10876
+ if (name === "claude-md" && rootClaudeMdExists) {
10877
+ categories.push({ name, label: def.label, conflicts: [] });
10878
+ continue;
10879
+ }
10880
+ const conflicts = [];
10881
+ for (const relPath of def.files) {
10882
+ const fullPath = join2(targetDir, relPath);
10883
+ if (existsSync2(fullPath)) {
10884
+ const stat = statSync(fullPath);
10885
+ conflicts.push({ path: relPath, size: stat.size });
10886
+ }
10887
+ }
10888
+ categories.push({ name, label: def.label, conflicts });
10889
+ totalConflicts += conflicts.length;
10890
+ }
10891
+ if (rootClaudeMdExists) {
10892
+ const stat = statSync(join2(targetDir, "CLAUDE.md"));
10893
+ categories.unshift({
10894
+ name: "root-claude-md",
10895
+ label: "CLAUDE.md (root)",
10896
+ conflicts: [{ path: "CLAUDE.md", size: stat.size }]
10897
+ });
10898
+ totalConflicts += 1;
10899
+ }
10900
+ const docsReadmeExists = existsSync2(join2(targetDir, "docs", "README.md"));
10901
+ return {
10902
+ categories,
10903
+ hasConflicts: totalConflicts > 0,
10904
+ totalConflicts,
10905
+ docsReadmeExists,
10906
+ rootClaudeMdExists
10907
+ };
10908
+ }
10909
+
10910
+ // src/generators/backup-manager.ts
10911
+ import { mkdir as mkdir2, copyFile, writeFile as writeFile2, rename } from "fs/promises";
10912
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
10913
+ import { join as join3, dirname as dirname2 } from "path";
10914
+ async function createBackup(targetDir, conflictingFiles) {
10915
+ const now = /* @__PURE__ */ new Date();
10916
+ const timestamp = now.toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
10917
+ const backupDir = join3(targetDir, ".fishi", "backup", timestamp);
10918
+ await mkdir2(backupDir, { recursive: true });
10919
+ const manifestFiles = [];
10920
+ for (const relPath of conflictingFiles) {
10921
+ const srcPath = join3(targetDir, relPath);
10922
+ const destPath = join3(backupDir, relPath);
10923
+ if (existsSync3(srcPath)) {
10924
+ await mkdir2(dirname2(destPath), { recursive: true });
10925
+ await copyFile(srcPath, destPath);
10926
+ const stat = statSync2(srcPath);
10927
+ manifestFiles.push({ path: relPath, size: stat.size });
10928
+ }
10929
+ }
10930
+ const fishiVersion = "0.3.0";
10931
+ const manifest = {
10932
+ timestamp: now.toISOString(),
10933
+ fishi_version: fishiVersion,
10934
+ files: manifestFiles
10935
+ };
10936
+ const manifestPath = join3(backupDir, "manifest.json");
10937
+ const tmpPath = manifestPath + ".tmp";
10938
+ await writeFile2(tmpPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
10939
+ await rename(tmpPath, manifestPath);
10940
+ return backupDir;
10941
+ }
10651
10942
  export {
10652
10943
  architectAgentTemplate,
10653
10944
  backendAgentTemplate,
10945
+ createBackup,
10946
+ detectConflicts,
10654
10947
  devLeadTemplate,
10655
10948
  devopsAgentTemplate,
10656
10949
  docsAgentTemplate,
@@ -10704,6 +10997,11 @@ export {
10704
10997
  getWorktreeManagerScript,
10705
10998
  getWorktreeSetupHook,
10706
10999
  marketingAgentTemplate,
11000
+ mergeClaudeMd,
11001
+ mergeClaudeMdTop,
11002
+ mergeGitignore,
11003
+ mergeMcpJson,
11004
+ mergeSettingsJson,
10707
11005
  opsLeadTemplate,
10708
11006
  planningAgentTemplate,
10709
11007
  planningLeadTemplate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlucent/fishi-core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared templates, types, and generators for the FISHI framework",
5
5
  "license": "MIT",
6
6
  "type": "module",