@qlucent/fishi-core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -441,10 +441,17 @@ 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;
448
455
  }
449
456
  interface ScaffoldResult {
450
457
  agentCount: number;
@@ -455,4 +462,55 @@ interface ScaffoldResult {
455
462
  }
456
463
  declare function generateScaffold(targetDir: string, options: ScaffoldOptions): Promise<ScaffoldResult>;
457
464
 
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 };
465
+ interface FileConflict {
466
+ path: string;
467
+ size: number;
468
+ }
469
+ interface ConflictCategory {
470
+ name: string;
471
+ label: string;
472
+ conflicts: FileConflict[];
473
+ }
474
+ interface ConflictMap {
475
+ categories: ConflictCategory[];
476
+ hasConflicts: boolean;
477
+ totalConflicts: number;
478
+ docsReadmeExists: boolean;
479
+ }
480
+ declare function detectConflicts(targetDir: string): ConflictMap;
481
+
482
+ interface BackupManifest {
483
+ timestamp: string;
484
+ fishi_version: string;
485
+ files: {
486
+ path: string;
487
+ size: number;
488
+ }[];
489
+ }
490
+ declare function createBackup(targetDir: string, conflictingFiles: string[]): Promise<string>;
491
+
492
+ declare function mergeClaudeMd(existing: string, fishiContent: string): string;
493
+ interface HookEntry {
494
+ matcher: string;
495
+ command: string;
496
+ [key: string]: unknown;
497
+ }
498
+ interface SettingsJson {
499
+ hooks?: Record<string, HookEntry[]>;
500
+ permissions?: {
501
+ allow?: string[];
502
+ deny?: string[];
503
+ };
504
+ env?: Record<string, string>;
505
+ enabledPlugins?: string[];
506
+ [key: string]: unknown;
507
+ }
508
+ declare function mergeSettingsJson(existing: SettingsJson, fishi: SettingsJson): SettingsJson;
509
+ interface McpJson {
510
+ mcpServers: Record<string, Record<string, unknown>>;
511
+ [key: string]: unknown;
512
+ }
513
+ declare function mergeMcpJson(existing: McpJson, fishi: McpJson): McpJson;
514
+ declare function mergeGitignore(existing: string, fishiAdditions: string): string;
515
+
516
+ 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, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, researchAgentTemplate, securityAgentTemplate, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
package/dist/index.js CHANGED
@@ -10375,12 +10375,96 @@ 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 mergeClaudeMd(existing, fishiContent) {
10387
+ const section = `
10388
+ ---
10389
+ ### FISHI Framework
10390
+ ${FISHI_START}
10391
+ ${fishiContent}
10392
+ ${FISHI_END}`;
10393
+ const startIdx = existing.indexOf(FISHI_START);
10394
+ const endIdx = existing.indexOf(FISHI_END);
10395
+ if (startIdx !== -1 && endIdx !== -1) {
10396
+ let sectionStart = existing.lastIndexOf("\n---\n### FISHI Framework", startIdx);
10397
+ if (sectionStart === -1) sectionStart = startIdx;
10398
+ const sectionEnd = endIdx + FISHI_END.length;
10399
+ return existing.slice(0, sectionStart) + section + existing.slice(sectionEnd);
10400
+ }
10401
+ return existing + section;
10402
+ }
10403
+ function mergeSettingsJson(existing, fishi) {
10404
+ const result = { ...existing };
10405
+ const mergedHooks = { ...existing.hooks };
10406
+ if (fishi.hooks) {
10407
+ for (const [event, entries] of Object.entries(fishi.hooks)) {
10408
+ if (!mergedHooks[event]) {
10409
+ mergedHooks[event] = [...entries];
10410
+ } else {
10411
+ for (const entry of entries) {
10412
+ const isDuplicate = mergedHooks[event].some(
10413
+ (e) => e.matcher === entry.matcher && e.command === entry.command
10414
+ );
10415
+ if (!isDuplicate) {
10416
+ mergedHooks[event] = [...mergedHooks[event], entry];
10417
+ }
10418
+ }
10419
+ }
10420
+ }
10421
+ }
10422
+ result.hooks = mergedHooks;
10423
+ const existingAllow = existing.permissions?.allow || [];
10424
+ const fishiAllow = fishi.permissions?.allow || [];
10425
+ const existingDeny = existing.permissions?.deny || [];
10426
+ const fishiDeny = fishi.permissions?.deny || [];
10427
+ result.permissions = {
10428
+ allow: [.../* @__PURE__ */ new Set([...existingAllow, ...fishiAllow])],
10429
+ deny: [.../* @__PURE__ */ new Set([...existingDeny, ...fishiDeny])]
10430
+ };
10431
+ if (fishi.env) {
10432
+ result.env = { ...fishi.env, ...existing.env };
10433
+ }
10434
+ if (fishi.enabledPlugins) {
10435
+ result.enabledPlugins = [
10436
+ .../* @__PURE__ */ new Set([...existing.enabledPlugins || [], ...fishi.enabledPlugins])
10437
+ ];
10438
+ }
10439
+ return result;
10440
+ }
10441
+ function mergeMcpJson(existing, fishi) {
10442
+ const merged = { ...existing, mcpServers: { ...existing.mcpServers } };
10443
+ for (const [name, config] of Object.entries(fishi.mcpServers)) {
10444
+ if (!(name in merged.mcpServers)) {
10445
+ merged.mcpServers[name] = config;
10446
+ }
10447
+ }
10448
+ return merged;
10449
+ }
10450
+ function mergeGitignore(existing, fishiAdditions) {
10451
+ if (existing.includes(".trees/")) {
10452
+ return existing;
10453
+ }
10454
+ return existing + fishiAdditions;
10455
+ }
10456
+
10457
+ // src/generators/scaffold.ts
10381
10458
  async function generateScaffold(targetDir, options) {
10382
10459
  let filesCreated = 0;
10383
- async function write(relativePath, content) {
10460
+ const resolutions = options.resolutions;
10461
+ async function write(relativePath, content, category) {
10462
+ if (resolutions) {
10463
+ const fileRes = resolutions.files[relativePath];
10464
+ const catRes = category ? resolutions.categories[category] : void 0;
10465
+ const resolution = fileRes || catRes;
10466
+ if (resolution === "skip") return;
10467
+ }
10384
10468
  const fullPath = join(targetDir, relativePath);
10385
10469
  await mkdir(dirname(fullPath), { recursive: true });
10386
10470
  await writeFile(fullPath, content, "utf-8");
@@ -10431,39 +10515,39 @@ async function generateScaffold(targetDir, options) {
10431
10515
  for (const dir of dirs) {
10432
10516
  await mkdir(join(targetDir, dir), { recursive: true });
10433
10517
  }
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));
10518
+ await write(".claude/agents/master-orchestrator.md", getMasterOrchestratorTemplate(), "agents");
10519
+ await write(".claude/agents/coordinators/planning-lead.md", planningLeadTemplate(ctx), "agents");
10520
+ await write(".claude/agents/coordinators/dev-lead.md", devLeadTemplate(ctx), "agents");
10521
+ await write(".claude/agents/coordinators/quality-lead.md", qualityLeadTemplate(ctx), "agents");
10522
+ await write(".claude/agents/coordinators/ops-lead.md", opsLeadTemplate(ctx), "agents");
10523
+ await write(".claude/agents/research-agent.md", researchAgentTemplate(ctx), "agents");
10524
+ await write(".claude/agents/planning-agent.md", planningAgentTemplate(ctx), "agents");
10525
+ await write(".claude/agents/architect-agent.md", architectAgentTemplate(ctx), "agents");
10526
+ await write(".claude/agents/backend-agent.md", backendAgentTemplate(ctx), "agents");
10527
+ await write(".claude/agents/frontend-agent.md", frontendAgentTemplate(ctx), "agents");
10528
+ await write(".claude/agents/uiux-agent.md", uiuxAgentTemplate(ctx), "agents");
10529
+ await write(".claude/agents/fullstack-agent.md", fullstackAgentTemplate(ctx), "agents");
10530
+ await write(".claude/agents/devops-agent.md", devopsAgentTemplate(ctx), "agents");
10531
+ await write(".claude/agents/testing-agent.md", testingAgentTemplate(ctx), "agents");
10532
+ await write(".claude/agents/security-agent.md", securityAgentTemplate(ctx), "agents");
10533
+ await write(".claude/agents/docs-agent.md", docsAgentTemplate(ctx), "agents");
10534
+ await write(".claude/agents/writing-agent.md", writingAgentTemplate(ctx), "agents");
10535
+ await write(".claude/agents/marketing-agent.md", marketingAgentTemplate(ctx), "agents");
10452
10536
  const agentCount = 18;
10453
10537
  await write(".fishi/agent-factory/agent-template.md", getAgentFactoryTemplate());
10454
10538
  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());
10539
+ await write(".claude/skills/brainstorming/SKILL.md", getBrainstormingSkill(), "skills");
10540
+ await write(".claude/skills/brownfield-analysis/SKILL.md", getBrownfieldAnalysisSkill(), "skills");
10541
+ await write(".claude/skills/taskboard-ops/SKILL.md", getTaskboardOpsSkill(), "skills");
10542
+ await write(".claude/skills/code-gen/SKILL.md", getCodeGenSkill(), "skills");
10543
+ await write(".claude/skills/debugging/SKILL.md", getDebuggingSkill(), "skills");
10544
+ await write(".claude/skills/api-design/SKILL.md", getApiDesignSkill(), "skills");
10545
+ await write(".claude/skills/testing/SKILL.md", getTestingSkill(), "skills");
10546
+ await write(".claude/skills/deployment/SKILL.md", getDeploymentSkill(), "skills");
10547
+ await write(".claude/skills/prd/SKILL.md", getPrdSkill(), "skills");
10548
+ await write(".claude/skills/brownfield-discovery/SKILL.md", getBrownfieldDiscoverySkill(), "skills");
10549
+ await write(".claude/skills/adaptive-taskgraph/SKILL.md", getAdaptiveTaskGraphSkill(), "skills");
10550
+ await write(".claude/skills/documentation/SKILL.md", getDocumentationSkill(), "skills");
10467
10551
  const skillCount = 12;
10468
10552
  await write(".fishi/scripts/session-start.mjs", getSessionStartHook());
10469
10553
  await write(".fishi/scripts/auto-checkpoint.mjs", getAutoCheckpointHook());
@@ -10496,14 +10580,14 @@ async function generateScaffold(targetDir, options) {
10496
10580
  await write(".fishi/todos/agents/frontend-agent.md", todoTemplate("frontend-agent"));
10497
10581
  await write(".fishi/todos/agents/testing-agent.md", todoTemplate("testing-agent"));
10498
10582
  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());
10583
+ await write(".claude/commands/fishi-init.md", getInitCommand(), "commands");
10584
+ await write(".claude/commands/fishi-status.md", getStatusCommand(), "commands");
10585
+ await write(".claude/commands/fishi-resume.md", getResumeCommand(), "commands");
10586
+ await write(".claude/commands/fishi-gate.md", getGateCommand(), "commands");
10587
+ await write(".claude/commands/fishi-board.md", getBoardCommand(), "commands");
10588
+ await write(".claude/commands/fishi-sprint.md", getSprintCommand(), "commands");
10589
+ await write(".claude/commands/fishi-reset.md", getResetCommand(), "commands");
10590
+ await write(".claude/commands/fishi-prd.md", getPrdCommand(), "commands");
10507
10591
  const commandCount = 8;
10508
10592
  await write(".fishi/fishi.yaml", getFishiYamlTemplate({
10509
10593
  projectName: options.projectName,
@@ -10513,16 +10597,37 @@ async function generateScaffold(targetDir, options) {
10513
10597
  language: options.language,
10514
10598
  framework: options.framework
10515
10599
  }));
10516
- await write(".claude/settings.json", getSettingsJsonTemplate());
10517
- await write(".claude/CLAUDE.md", getClaudeMdTemplate({
10600
+ const settingsContent = getSettingsJsonTemplate();
10601
+ if (resolutions?.categories["settings-json"] === "merge") {
10602
+ const existingRaw = await fsReadFile(join(targetDir, ".claude", "settings.json"), "utf-8");
10603
+ const merged = mergeSettingsJson(JSON.parse(existingRaw), JSON.parse(settingsContent));
10604
+ await write(".claude/settings.json", JSON.stringify(merged, null, 2) + "\n", "settings-json");
10605
+ } else {
10606
+ await write(".claude/settings.json", settingsContent, "settings-json");
10607
+ }
10608
+ const claudeMdContent = getClaudeMdTemplate({
10518
10609
  projectName: options.projectName,
10519
10610
  projectDescription: ctx.projectDescription,
10520
10611
  projectType: options.projectType,
10521
10612
  language: options.language,
10522
10613
  framework: options.framework,
10523
10614
  brownfieldAnalysis: options.brownfieldAnalysis
10524
- }));
10525
- await write(".mcp.json", getMcpJsonTemplate());
10615
+ });
10616
+ if (resolutions?.categories["claude-md"] === "merge") {
10617
+ const existingMd = await fsReadFile(join(targetDir, ".claude", "CLAUDE.md"), "utf-8");
10618
+ const merged = mergeClaudeMd(existingMd, claudeMdContent);
10619
+ await write(".claude/CLAUDE.md", merged, "claude-md");
10620
+ } else {
10621
+ await write(".claude/CLAUDE.md", claudeMdContent, "claude-md");
10622
+ }
10623
+ const mcpContent = getMcpJsonTemplate();
10624
+ if (resolutions?.categories["mcp-json"] === "merge") {
10625
+ const existingRaw = await fsReadFile(join(targetDir, ".mcp.json"), "utf-8");
10626
+ const merged = mergeMcpJson(JSON.parse(existingRaw), JSON.parse(mcpContent));
10627
+ await write(".mcp.json", JSON.stringify(merged, null, 2) + "\n", "mcp-json");
10628
+ } else {
10629
+ await write(".mcp.json", mcpContent, "mcp-json");
10630
+ }
10526
10631
  await write(".fishi/state/project.yaml", getProjectYamlTemplate({
10527
10632
  projectName: options.projectName,
10528
10633
  projectDescription: ctx.projectDescription,
@@ -10537,18 +10642,24 @@ async function generateScaffold(targetDir, options) {
10537
10642
  await write(".fishi/memory/decisions.md", "# Architecture Decision Log\n\n_No decisions recorded yet._\n");
10538
10643
  await write(".fishi/memory/agents/master-orchestrator.md", getMasterOrchestratorMemory(ctx));
10539
10644
  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}
10645
+ if (!options.docsReadmeExists) {
10646
+ await write("docs/README.md", `# ${options.projectName}
10541
10647
 
10542
10648
  Documentation will be generated as the project progresses.
10543
10649
  `);
10650
+ }
10544
10651
  await write(".fishi/taskboard/board.md", getEmptyBoard());
10545
10652
  await write(".fishi/taskboard/backlog.md", getEmptyBacklog());
10546
10653
  const gitignorePath = join(targetDir, ".gitignore");
10547
10654
  const additions = getGitignoreAdditions();
10548
10655
  if (existsSync(gitignorePath)) {
10549
- const existing = await readFile(gitignorePath, "utf-8");
10550
- if (!existing.includes(".trees/")) {
10551
- await appendFile(gitignorePath, "\n" + additions, "utf-8");
10656
+ if (resolutions?.categories["gitignore"] === "skip") {
10657
+ } else {
10658
+ const existing = await readFile(gitignorePath, "utf-8");
10659
+ const merged = mergeGitignore(existing, "\n" + additions);
10660
+ if (merged !== existing) {
10661
+ await writeFile(join(targetDir, ".gitignore"), merged, "utf-8");
10662
+ }
10552
10663
  }
10553
10664
  } else {
10554
10665
  await writeFile(gitignorePath, additions, "utf-8");
@@ -10648,9 +10759,142 @@ Prioritized list of all upcoming work items.
10648
10759
  _Backlog is empty. Use /fishi-init to start the planning process._
10649
10760
  `;
10650
10761
  }
10762
+
10763
+ // src/generators/conflict-detector.ts
10764
+ import { existsSync as existsSync2, statSync } from "fs";
10765
+ import { join as join2 } from "path";
10766
+ var FISHI_FILES = {
10767
+ "claude-md": {
10768
+ label: ".claude/CLAUDE.md",
10769
+ files: [".claude/CLAUDE.md"]
10770
+ },
10771
+ "settings-json": {
10772
+ label: ".claude/settings.json",
10773
+ files: [".claude/settings.json"]
10774
+ },
10775
+ "mcp-json": {
10776
+ label: ".mcp.json",
10777
+ files: [".mcp.json"]
10778
+ },
10779
+ "agents": {
10780
+ label: "Agents",
10781
+ files: [
10782
+ ".claude/agents/master-orchestrator.md",
10783
+ ".claude/agents/coordinators/planning-lead.md",
10784
+ ".claude/agents/coordinators/dev-lead.md",
10785
+ ".claude/agents/coordinators/quality-lead.md",
10786
+ ".claude/agents/coordinators/ops-lead.md",
10787
+ ".claude/agents/research-agent.md",
10788
+ ".claude/agents/planning-agent.md",
10789
+ ".claude/agents/architect-agent.md",
10790
+ ".claude/agents/backend-agent.md",
10791
+ ".claude/agents/frontend-agent.md",
10792
+ ".claude/agents/uiux-agent.md",
10793
+ ".claude/agents/fullstack-agent.md",
10794
+ ".claude/agents/devops-agent.md",
10795
+ ".claude/agents/testing-agent.md",
10796
+ ".claude/agents/security-agent.md",
10797
+ ".claude/agents/docs-agent.md",
10798
+ ".claude/agents/writing-agent.md",
10799
+ ".claude/agents/marketing-agent.md"
10800
+ ]
10801
+ },
10802
+ "skills": {
10803
+ label: "Skills",
10804
+ files: [
10805
+ ".claude/skills/brainstorming/SKILL.md",
10806
+ ".claude/skills/brownfield-analysis/SKILL.md",
10807
+ ".claude/skills/taskboard-ops/SKILL.md",
10808
+ ".claude/skills/code-gen/SKILL.md",
10809
+ ".claude/skills/debugging/SKILL.md",
10810
+ ".claude/skills/api-design/SKILL.md",
10811
+ ".claude/skills/testing/SKILL.md",
10812
+ ".claude/skills/deployment/SKILL.md",
10813
+ ".claude/skills/prd/SKILL.md",
10814
+ ".claude/skills/brownfield-discovery/SKILL.md",
10815
+ ".claude/skills/adaptive-taskgraph/SKILL.md",
10816
+ ".claude/skills/documentation/SKILL.md"
10817
+ ]
10818
+ },
10819
+ "commands": {
10820
+ label: "Commands",
10821
+ files: [
10822
+ ".claude/commands/fishi-init.md",
10823
+ ".claude/commands/fishi-status.md",
10824
+ ".claude/commands/fishi-resume.md",
10825
+ ".claude/commands/fishi-gate.md",
10826
+ ".claude/commands/fishi-board.md",
10827
+ ".claude/commands/fishi-sprint.md",
10828
+ ".claude/commands/fishi-reset.md",
10829
+ ".claude/commands/fishi-prd.md"
10830
+ ]
10831
+ },
10832
+ "gitignore": {
10833
+ label: ".gitignore",
10834
+ files: [".gitignore"]
10835
+ }
10836
+ };
10837
+ function detectConflicts(targetDir) {
10838
+ const categories = [];
10839
+ let totalConflicts = 0;
10840
+ for (const [name, def] of Object.entries(FISHI_FILES)) {
10841
+ const conflicts = [];
10842
+ for (const relPath of def.files) {
10843
+ const fullPath = join2(targetDir, relPath);
10844
+ if (existsSync2(fullPath)) {
10845
+ const stat = statSync(fullPath);
10846
+ conflicts.push({ path: relPath, size: stat.size });
10847
+ }
10848
+ }
10849
+ categories.push({ name, label: def.label, conflicts });
10850
+ totalConflicts += conflicts.length;
10851
+ }
10852
+ const docsReadmeExists = existsSync2(join2(targetDir, "docs", "README.md"));
10853
+ return {
10854
+ categories,
10855
+ hasConflicts: totalConflicts > 0,
10856
+ totalConflicts,
10857
+ docsReadmeExists
10858
+ };
10859
+ }
10860
+
10861
+ // src/generators/backup-manager.ts
10862
+ import { mkdir as mkdir2, copyFile, writeFile as writeFile2, rename } from "fs/promises";
10863
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
10864
+ import { join as join3, dirname as dirname2 } from "path";
10865
+ async function createBackup(targetDir, conflictingFiles) {
10866
+ const now = /* @__PURE__ */ new Date();
10867
+ const timestamp = now.toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
10868
+ const backupDir = join3(targetDir, ".fishi", "backup", timestamp);
10869
+ await mkdir2(backupDir, { recursive: true });
10870
+ const manifestFiles = [];
10871
+ for (const relPath of conflictingFiles) {
10872
+ const srcPath = join3(targetDir, relPath);
10873
+ const destPath = join3(backupDir, relPath);
10874
+ if (existsSync3(srcPath)) {
10875
+ await mkdir2(dirname2(destPath), { recursive: true });
10876
+ await copyFile(srcPath, destPath);
10877
+ const stat = statSync2(srcPath);
10878
+ manifestFiles.push({ path: relPath, size: stat.size });
10879
+ }
10880
+ }
10881
+ const fishiVersion = "0.2.0";
10882
+ const manifest = {
10883
+ timestamp: now.toISOString(),
10884
+ fishi_version: fishiVersion,
10885
+ files: manifestFiles
10886
+ };
10887
+ const manifestPath = join3(backupDir, "manifest.json");
10888
+ const tmpPath = manifestPath + ".tmp";
10889
+ await writeFile2(tmpPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
10890
+ await rename(tmpPath, manifestPath);
10891
+ return backupDir;
10892
+ }
10651
10893
  export {
10652
10894
  architectAgentTemplate,
10653
10895
  backendAgentTemplate,
10896
+ createBackup,
10897
+ detectConflicts,
10654
10898
  devLeadTemplate,
10655
10899
  devopsAgentTemplate,
10656
10900
  docsAgentTemplate,
@@ -10704,6 +10948,10 @@ export {
10704
10948
  getWorktreeManagerScript,
10705
10949
  getWorktreeSetupHook,
10706
10950
  marketingAgentTemplate,
10951
+ mergeClaudeMd,
10952
+ mergeGitignore,
10953
+ mergeMcpJson,
10954
+ mergeSettingsJson,
10707
10955
  opsLeadTemplate,
10708
10956
  planningAgentTemplate,
10709
10957
  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.2.0",
4
4
  "description": "Shared templates, types, and generators for the FISHI framework",
5
5
  "license": "MIT",
6
6
  "type": "module",