@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 +69 -1
- package/dist/index.js +346 -48
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
10517
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10550
|
-
|
|
10551
|
-
await
|
|
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,
|