chati-dev 4.2.2 → 4.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/README.md +80 -53
- package/bin/chati.js +150 -5
- package/framework/agents/build/dev.md +509 -76
- package/framework/agents/deploy/devops.md +40 -25
- package/framework/agents/discover/brief.md +156 -22
- package/framework/agents/discover/brownfield-wu.md +24 -14
- package/framework/agents/discover/greenfield-wu.md +100 -14
- package/framework/agents/plan/architect-data-engineer.md +6 -6
- package/framework/agents/plan/architect-system.md +46 -12
- package/framework/agents/plan/architect.md +40 -20
- package/framework/agents/plan/detail.md +36 -24
- package/framework/agents/plan/phases.md +36 -26
- package/framework/agents/plan/tasks.md +114 -33
- package/framework/agents/plan/ux-brand-architect.md +240 -8
- package/framework/agents/plan/ux-component-engineer.md +28 -13
- package/framework/agents/plan/ux-researcher.md +7 -6
- package/framework/agents/plan/ux.md +55 -22
- package/framework/agents/quality/qa-implementation.md +143 -74
- package/framework/agents/quality/qa-planning.md +115 -42
- package/framework/agents/quality/qa-visual.md +439 -0
- package/framework/agents/shared/visualizer.md +128 -0
- package/framework/config.yaml +7 -6
- package/framework/constitution.md +127 -44
- package/framework/context/governance.md +12 -7
- package/framework/context/quality.md +6 -5
- package/framework/context/root.md +6 -6
- package/framework/data/entity-registry.yaml +377 -4
- package/framework/data/model-limits.json +19 -0
- package/framework/domains/agents/qa-visual.yaml +74 -0
- package/framework/domains/constitution.yaml +46 -2
- package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
- package/framework/hooks/advance-trigger.js +131 -0
- package/framework/hooks/brief-validator.js +83 -0
- package/framework/hooks/constitution-guard.js +24 -5
- package/framework/hooks/license-guard.js +62 -27
- package/framework/hooks/mode-governance.js +13 -2
- package/framework/hooks/model-governance.js +1 -0
- package/framework/hooks/post-dev.js +207 -0
- package/framework/hooks/prism-engine.js +274 -105
- package/framework/hooks/reasoning-escalator.js +371 -0
- package/framework/hooks/reference-trigger.js +117 -0
- package/framework/hooks/session-digest.js +50 -1
- package/framework/hooks/settings.json +32 -1
- package/framework/hooks/style-guard.js +25 -6
- package/framework/hooks/team-quality-gate.js +19 -12
- package/framework/hooks/undercover-guard.js +4 -2
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/context-engine.md +4 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/intelligence/hooks-performance.md +3 -3
- package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
- package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
- package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
- package/framework/orchestrator/chati-router.js +387 -0
- package/framework/orchestrator/chati-update.md +40 -40
- package/framework/orchestrator/chati.md +294 -94
- package/framework/scaffold/motion-premium/README.md +89 -0
- package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
- package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
- package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
- package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
- package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
- package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
- package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
- package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
- package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
- package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
- package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
- package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
- package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
- package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
- package/framework/scaffold/motion-premium-3d/README.md +80 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
- package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
- package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
- package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
- package/framework/schemas/session.schema.json +109 -21
- package/framework/scripts/reference-capture.js +430 -0
- package/framework/scripts/visual-qa.js +674 -0
- package/framework/tasks/orchestrator-handoff.md +1 -1
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/orchestrator-route.md +1 -1
- package/framework/tasks/orchestrator-status.md +3 -3
- package/framework/tasks/qa-planning-gate-define.md +1 -1
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/brandbook-tmpl.yaml +1 -1
- package/framework/templates/component-spec-tmpl.yaml +1 -1
- package/framework/templates/design-token-tmpl.yaml +1 -1
- package/framework/templates/icon-system-tmpl.yaml +1 -1
- package/framework/templates/team-planning-tasks.yaml +6 -5
- package/framework/workflows/brownfield-discovery.yaml +2 -2
- package/framework/workflows/brownfield-fullstack.yaml +15 -11
- package/framework/workflows/brownfield-service.yaml +14 -10
- package/framework/workflows/brownfield-ui.yaml +15 -11
- package/framework/workflows/greenfield-fullstack.yaml +16 -13
- package/framework/workflows/quick-flow.yaml +3 -3
- package/framework/workflows/standard-flow.yaml +12 -9
- package/package.json +10 -5
- package/src/autonomy/autonomous-gate.js +1 -0
- package/src/autonomy/build-state.js +1 -2
- package/src/autonomy/progress-reporter.js +1 -1
- package/src/config/agent-customizer.js +11 -3
- package/src/config/claude-settings-generator.js +27 -7
- package/src/config/context-file-generator.js +41 -21
- package/src/config/framework-adapter.js +1 -0
- package/src/config/gemini-hooks-generator.js +19 -7
- package/src/config/mcp-configs.js +1 -0
- package/src/context/layers/l1-global.js +2 -1
- package/src/dashboard/data-reader.js +4 -3
- package/src/dashboard/layout.js +2 -1
- package/src/decision/analyzer.js +6 -30
- package/src/decision/engine.js +4 -28
- package/src/decision/registry-healer.js +3 -2
- package/src/decision/registry-updater.js +23 -14
- package/src/extensions/loader.js +2 -8
- package/src/gates/g1-planning-complete.js +2 -1
- package/src/gates/g2-qa-planning.js +2 -1
- package/src/gates/g3-implementation.js +2 -1
- package/src/gates/g4-qa-implementation.js +3 -2
- package/src/gates/g5-deploy-ready.js +2 -1
- package/src/health/engine.js +4 -3
- package/src/installer/core.js +422 -81
- package/src/installer/preflight.js +131 -0
- package/src/installer/provider-overlay.js +3 -3
- package/src/installer/scaffold-applier.js +358 -0
- package/src/installer/templates.js +46 -29
- package/src/installer/validator.js +17 -12
- package/src/intelligence/registry-manager.js +22 -29
- package/src/intelligence/timeline.js +11 -6
- package/src/license/commands.js +1 -1
- package/src/license/wait.js +102 -0
- package/src/memory/agent-memory.js +81 -0
- package/src/memory/dream.js +32 -1
- package/src/merger/replace-merger.js +28 -15
- package/src/orchestrator/agent-selector.js +2 -1
- package/src/orchestrator/cli.js +1869 -71
- package/src/orchestrator/doctor.js +270 -0
- package/src/orchestrator/handoff-engine.js +4 -3
- package/src/orchestrator/index.js +2 -0
- package/src/orchestrator/pipeline-manager.js +306 -15
- package/src/orchestrator/session-manager.js +331 -6
- package/src/tasks/handoff.js +3 -2
- package/src/telemetry/config.js +4 -3
- package/src/telemetry/schema.js +1 -0
- package/src/terminal/collector.js +3 -2
- package/src/terminal/index.js +1 -2
- package/src/terminal/isolation.js +52 -18
- package/src/terminal/prompt-builder.js +42 -25
- package/src/terminal/run-parallel.js +1 -1
- package/src/terminal/run-team.js +3 -3
- package/src/terminal/team-task-list.js +43 -4
- package/src/upgrade/backup.js +3 -2
- package/src/upgrade/checker.js +3 -2
- package/src/upgrade/migrator.js +65 -7
- package/src/upgrade/tracked-files-detector.js +86 -0
- package/src/upgrade/user-messages.js +94 -0
- package/src/utils/config-parser.js +2 -1
- package/src/utils/feature-flags.js +2 -1
- package/src/utils/flatten-entities.js +69 -0
- package/src/utils/framework-dir.js +16 -0
- package/src/utils/model-id.js +85 -0
- package/src/utils/provider-limits.js +84 -23
- package/src/utils/schema-validator.js +1 -1
- package/src/wizard/i18n.js +5 -4
- package/src/wizard/index.js +14 -0
- package/assets/logo - c/303/263pia.png +0 -0
- package/assets/logo.svg +0 -42
- package/assets/logo2.png +0 -0
- package/assets/social-preview.png +0 -0
- package/scripts/bundle-framework.js +0 -69
- package/scripts/changelog-generator.js +0 -222
- package/scripts/codebase-mapper.js +0 -728
- package/scripts/commit-message-generator.js +0 -167
- package/scripts/coverage-analyzer.js +0 -260
- package/scripts/dependency-analyzer.js +0 -280
- package/scripts/doctor/checks/agents.js +0 -77
- package/scripts/doctor/checks/constitution.js +0 -41
- package/scripts/doctor/checks/domain-alignment.js +0 -58
- package/scripts/doctor/checks/prism-layers.js +0 -84
- package/scripts/doctor/checks/registry.js +0 -55
- package/scripts/doctor/checks/schemas.js +0 -61
- package/scripts/doctor/fixes/reference-fix.js +0 -100
- package/scripts/doctor/fixes/registry-fix.js +0 -56
- package/scripts/doctor/index.js +0 -212
- package/scripts/framework-analyzer.js +0 -308
- package/scripts/generate-constitution-domain.js +0 -253
- package/scripts/generate-signing-key.js +0 -33
- package/scripts/health-check.js +0 -481
- package/scripts/ide-sync.js +0 -326
- package/scripts/performance-analyzer.js +0 -325
- package/scripts/plan-tracker.js +0 -278
- package/scripts/populate-entity-registry.js +0 -481
- package/scripts/pr-review.js +0 -317
- package/scripts/rollback-manager.js +0 -310
- package/scripts/semantic-lint.js +0 -328
- package/scripts/sign-manifest.js +0 -53
- package/scripts/stuck-detector.js +0 -343
- package/scripts/test-quality-assessment.js +0 -257
- package/scripts/validate-agents.js +0 -368
- package/scripts/validate-package.js +0 -505
- package/scripts/validate-tasks.js +0 -465
- package/src/autonomy/worktree-manager.js +0 -250
- package/src/intelligence/decision-engine.js +0 -256
- package/src/intelligence/document-sharder.js +0 -221
- package/src/intelligence/elicitation.js +0 -265
package/src/installer/core.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
1
|
+
import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, dirname, basename } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createRequire } from 'module';
|
|
4
5
|
import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
|
|
5
6
|
import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
|
|
6
7
|
import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
|
|
@@ -22,7 +23,15 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
|
|
|
22
23
|
* Install Chati.dev framework into target directory
|
|
23
24
|
*/
|
|
24
25
|
export async function installFramework(config) {
|
|
25
|
-
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName,
|
|
26
|
+
const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, llmProvider, allProviders } = config;
|
|
27
|
+
// Always derive version from package.json (single source of truth)
|
|
28
|
+
let { version } = config;
|
|
29
|
+
if (!version) {
|
|
30
|
+
try {
|
|
31
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
32
|
+
version = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
|
|
33
|
+
} catch { version = 'unknown'; }
|
|
34
|
+
}
|
|
26
35
|
|
|
27
36
|
// 0. Verify framework signature (supply chain protection)
|
|
28
37
|
const manifestPath = join(FRAMEWORK_SOURCE, 'manifest.json');
|
|
@@ -40,17 +49,36 @@ export async function installFramework(config) {
|
|
|
40
49
|
|
|
41
50
|
// 1. Create .chati/ session directory
|
|
42
51
|
createDir(join(targetDir, '.chati'));
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const sessionPath = join(targetDir, '.chati', 'session.yaml');
|
|
53
|
+
// Preserve existing session.yaml on reinstall/recovery (don't destroy pipeline state).
|
|
54
|
+
// Only write fresh session if one does not exist.
|
|
55
|
+
if (!existsSync(sessionPath)) {
|
|
56
|
+
writeFileSync(
|
|
57
|
+
sessionPath,
|
|
58
|
+
generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders }),
|
|
59
|
+
'utf-8'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
48
62
|
|
|
49
|
-
// 2. Create chati.dev/ framework directory (
|
|
50
|
-
|
|
63
|
+
// 2. Create .chati.dev/ framework directory (hidden — dot prefix)
|
|
64
|
+
// The source is chati.dev/ in the monorepo; installed as .chati.dev/ in user projects.
|
|
65
|
+
// Text replacement during copy handles all internal path references.
|
|
66
|
+
const frameworkDir = join(targetDir, '.chati.dev');
|
|
51
67
|
createDir(frameworkDir);
|
|
52
68
|
|
|
53
|
-
//
|
|
69
|
+
// 2b. Create artifacts/ at PROJECT ROOT (visible to user — this is their output)
|
|
70
|
+
const artifactsDir = join(targetDir, 'artifacts');
|
|
71
|
+
const artifactDirs = [
|
|
72
|
+
'0-WU', '1-Brief', '2-PRD',
|
|
73
|
+
'3-Architecture', '4-UX', '5-Phases',
|
|
74
|
+
'6-Tasks', '7-QA-Planning', '8-QA-Visual', '9-QA-Implementation', '10-Deploy',
|
|
75
|
+
'handoffs', 'decisions',
|
|
76
|
+
];
|
|
77
|
+
for (const dir of artifactDirs) {
|
|
78
|
+
createDir(join(artifactsDir, dir));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Copy framework structure (everything EXCEPT artifacts, which live at root)
|
|
54
82
|
const frameworkDirs = [
|
|
55
83
|
'orchestrator',
|
|
56
84
|
'agents/discover', 'agents/plan', 'agents/quality', 'agents/build', 'agents/deploy',
|
|
@@ -58,10 +86,6 @@ export async function installFramework(config) {
|
|
|
58
86
|
'schemas', 'frameworks', 'intelligence', 'patterns',
|
|
59
87
|
'hooks', 'domains', 'domains/agents', 'domains/workflows',
|
|
60
88
|
'i18n', 'migrations', 'data', 'context',
|
|
61
|
-
'artifacts/0-WU', 'artifacts/1-Brief', 'artifacts/2-PRD',
|
|
62
|
-
'artifacts/3-Architecture', 'artifacts/4-UX', 'artifacts/5-Phases',
|
|
63
|
-
'artifacts/6-Tasks', 'artifacts/7-QA-Planning', 'artifacts/9-QA-Implementation', 'artifacts/10-Deploy',
|
|
64
|
-
'artifacts/handoffs', 'artifacts/decisions',
|
|
65
89
|
];
|
|
66
90
|
|
|
67
91
|
for (const dir of frameworkDirs) {
|
|
@@ -82,6 +106,7 @@ export async function installFramework(config) {
|
|
|
82
106
|
'tasks/durable', 'tasks/daily',
|
|
83
107
|
'qa-planning/durable', 'qa-planning/daily',
|
|
84
108
|
'qa-implementation/durable', 'qa-implementation/daily',
|
|
109
|
+
'qa-visual/durable', 'qa-visual/daily',
|
|
85
110
|
'dev/durable', 'dev/daily',
|
|
86
111
|
'devops/durable', 'devops/daily',
|
|
87
112
|
];
|
|
@@ -97,6 +122,31 @@ export async function installFramework(config) {
|
|
|
97
122
|
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, llmProvider || 'claude', allProviders);
|
|
98
123
|
}
|
|
99
124
|
|
|
125
|
+
// Bundle the CLI source into the framework dir so chati-router.js can run
|
|
126
|
+
// commands locally without depending on `npx chati-dev` (which fetches from
|
|
127
|
+
// the npm registry — version drift causes inconsistent behavior).
|
|
128
|
+
// Result: every install is self-contained; no network/cache dependency at runtime.
|
|
129
|
+
copyCliSource(
|
|
130
|
+
join(__dirname, '..'), // packages/chati-dev/src/
|
|
131
|
+
join(frameworkDir, '_cli') // .chati.dev/_cli/
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// The bundled CLI imports ../../framework/hooks/model-governance.js relative
|
|
135
|
+
// to _cli/terminal/prompt-builder.js. Starting at .chati.dev/_cli/terminal/,
|
|
136
|
+
// `../../` resolves to .chati.dev/ — so the hooks copy must land at
|
|
137
|
+
// .chati.dev/framework/hooks/ to satisfy the import path.
|
|
138
|
+
copyDirVerbatim(
|
|
139
|
+
join(__dirname, '..', '..', 'framework', 'hooks'),
|
|
140
|
+
join(frameworkDir, 'framework', 'hooks')
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Also bundle production deps so the CLI can import js-yaml/chalk/etc. at runtime
|
|
144
|
+
// without relying on a node_modules tree in the user's project.
|
|
145
|
+
copyCliDependencies(
|
|
146
|
+
join(__dirname, '..', '..'), // packages/chati-dev/
|
|
147
|
+
join(frameworkDir, '_cli', 'node_modules') // .chati.dev/_cli/node_modules/
|
|
148
|
+
);
|
|
149
|
+
|
|
100
150
|
// Write config.yaml
|
|
101
151
|
writeFileSync(
|
|
102
152
|
join(frameworkDir, 'config.yaml'),
|
|
@@ -110,8 +160,8 @@ export async function installFramework(config) {
|
|
|
110
160
|
const ideProvider = IDE_TO_PROVIDER[ideKey];
|
|
111
161
|
const isSecondary = ideProvider && ideProvider !== primary && allProviders && allProviders.length > 1;
|
|
112
162
|
const orchestratorPath = isSecondary
|
|
113
|
-
?
|
|
114
|
-
: 'chati.dev/orchestrator/chati.md';
|
|
163
|
+
? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
|
|
164
|
+
: '.chati.dev/orchestrator/chati.md';
|
|
115
165
|
await configureIDE(targetDir, ideKey, selectedMCPs, { orchestratorPath, isSecondary, providerName: ideProvider });
|
|
116
166
|
}
|
|
117
167
|
|
|
@@ -129,11 +179,22 @@ export async function installFramework(config) {
|
|
|
129
179
|
for (const file of contextFiles) {
|
|
130
180
|
const src = join(FRAMEWORK_SOURCE, 'context', file);
|
|
131
181
|
if (existsSync(src)) {
|
|
132
|
-
|
|
182
|
+
// Apply path replacement so rules reference .chati.dev/ not chati.dev/
|
|
183
|
+
let content = readFileSync(src, 'utf-8');
|
|
184
|
+
content = applyPathReplacement(content);
|
|
185
|
+
writeFileSync(join(claudeRulesDir, file), content, 'utf-8');
|
|
133
186
|
}
|
|
134
187
|
}
|
|
135
188
|
|
|
136
|
-
|
|
189
|
+
// Preserve existing CLAUDE.md on reinstall — user or magic-docs may have
|
|
190
|
+
// edited it. Section-aware merge if exists; fresh write if not.
|
|
191
|
+
const claudeMdPath = join(targetDir, 'CLAUDE.md');
|
|
192
|
+
if (existsSync(claudeMdPath)) {
|
|
193
|
+
const existingClaudeMd = readFileSync(claudeMdPath, 'utf-8');
|
|
194
|
+
writeFileSync(claudeMdPath, mergeClaudeMd(existingClaudeMd, baseContent), 'utf-8');
|
|
195
|
+
} else {
|
|
196
|
+
writeFileSync(claudeMdPath, baseContent, 'utf-8');
|
|
197
|
+
}
|
|
137
198
|
writeFileSync(join(targetDir, 'CLAUDE.local.md'), generateClaudeLocalMd(), 'utf-8');
|
|
138
199
|
}
|
|
139
200
|
|
|
@@ -172,6 +233,8 @@ const FRAMEWORK_DIRS_TO_COPY = [
|
|
|
172
233
|
'context',
|
|
173
234
|
'tasks',
|
|
174
235
|
'presets',
|
|
236
|
+
'scripts',
|
|
237
|
+
'scaffold',
|
|
175
238
|
];
|
|
176
239
|
|
|
177
240
|
/**
|
|
@@ -182,8 +245,39 @@ const FRAMEWORK_ROOT_FILES = [
|
|
|
182
245
|
];
|
|
183
246
|
|
|
184
247
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
248
|
+
* Text file extensions that get path replacement during copy.
|
|
249
|
+
* Binary files (.png, .woff2, etc.) are copied without modification.
|
|
250
|
+
*/
|
|
251
|
+
const TEXT_EXTENSIONS = new Set(['.md', '.js', '.yaml', '.yml', '.json', '.html', '.toml', '.txt', '.rules']);
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Apply installed-path replacement to file content.
|
|
255
|
+
*
|
|
256
|
+
* Source uses `chati.dev/` everywhere. Installed project uses:
|
|
257
|
+
* - `.chati.dev/` for framework files (hidden)
|
|
258
|
+
* - `artifacts/` at project root for user-visible output
|
|
259
|
+
*
|
|
260
|
+
* Order matters: replace `chati.dev/artifacts/` FIRST (→ `artifacts/`),
|
|
261
|
+
* then `chati.dev/` (→ `.chati.dev/`). Reversing would produce `.chati.dev/artifacts/`.
|
|
262
|
+
*/
|
|
263
|
+
function applyPathReplacement(content) {
|
|
264
|
+
return content
|
|
265
|
+
// Step 1: artifacts at root (must be before generic replace)
|
|
266
|
+
.replace(/chati\.dev\/artifacts\//g, 'artifacts/')
|
|
267
|
+
// Step 2: path strings with slash — but NOT if already prefixed with dot
|
|
268
|
+
// (negative lookbehind for any char that would create `.chati.dev` already)
|
|
269
|
+
.replace(/(?<!\.)chati\.dev\//g, '.chati.dev/')
|
|
270
|
+
// Step 3: quoted dir name in JS join() calls (WITHOUT slash)
|
|
271
|
+
// Only match 'chati.dev' (the DIRECTORY, with dot) not 'chati-dev' (the NPM PACKAGE, with dash)
|
|
272
|
+
// Also: avoid double-prefixing if already '.chati.dev' in source
|
|
273
|
+
.replace(/(?<!\.)'chati\.dev'/g, "'.chati.dev'")
|
|
274
|
+
.replace(/(?<!\.)"chati\.dev"/g, '".chati.dev"');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Recursively copy a directory from source to destination, applying:
|
|
279
|
+
* 1. Path replacement (chati.dev/ → .chati.dev/, artifacts/ at root)
|
|
280
|
+
* 2. Provider adaptation for non-Claude providers
|
|
187
281
|
*/
|
|
188
282
|
function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
189
283
|
if (!existsSync(srcDir)) return;
|
|
@@ -196,14 +290,24 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
|
196
290
|
const relPath = relBase ? join(relBase, entry.name) : entry.name;
|
|
197
291
|
|
|
198
292
|
if (entry.isDirectory()) {
|
|
293
|
+
// Skip artifacts/ — those live at project root, not inside .chati.dev/
|
|
294
|
+
if (entry.name === 'artifacts') continue;
|
|
199
295
|
copyDirRecursive(srcPath, destPath, provider, relPath);
|
|
200
296
|
} else if (entry.isFile()) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
297
|
+
const ext = entry.name.includes('.') ? '.' + entry.name.split('.').pop() : '';
|
|
298
|
+
const isText = TEXT_EXTENSIONS.has(ext);
|
|
299
|
+
|
|
300
|
+
if (isText) {
|
|
301
|
+
let content = readFileSync(srcPath, 'utf-8');
|
|
302
|
+
// Always apply path replacement for installed location
|
|
303
|
+
content = applyPathReplacement(content);
|
|
304
|
+
// Provider adaptation on top (for non-Claude)
|
|
305
|
+
if (provider !== 'claude' && ADAPTABLE_FILES.has(relPath)) {
|
|
306
|
+
content = adaptFrameworkFile(content, relPath, provider);
|
|
307
|
+
}
|
|
308
|
+
writeFileSync(destPath, content, 'utf-8');
|
|
205
309
|
} else {
|
|
206
|
-
//
|
|
310
|
+
// Binary file: direct copy
|
|
207
311
|
copyFileSync(srcPath, destPath);
|
|
208
312
|
}
|
|
209
313
|
}
|
|
@@ -222,18 +326,18 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
|
222
326
|
export function copyFrameworkFiles(destDir, provider = 'claude') {
|
|
223
327
|
if (!existsSync(FRAMEWORK_SOURCE)) return;
|
|
224
328
|
|
|
225
|
-
// Copy loose root files
|
|
329
|
+
// Copy loose root files (with path replacement)
|
|
226
330
|
for (const file of FRAMEWORK_ROOT_FILES) {
|
|
227
331
|
const src = join(FRAMEWORK_SOURCE, file);
|
|
228
332
|
if (!existsSync(src)) continue;
|
|
229
333
|
const dest = join(destDir, file);
|
|
230
334
|
createDir(dirname(dest));
|
|
335
|
+
let content = readFileSync(src, 'utf-8');
|
|
336
|
+
content = applyPathReplacement(content);
|
|
231
337
|
if (provider !== 'claude' && ADAPTABLE_FILES.has(file)) {
|
|
232
|
-
|
|
233
|
-
writeFileSync(dest, adaptFrameworkFile(content, file, provider), 'utf-8');
|
|
234
|
-
} else {
|
|
235
|
-
copyFileSync(src, dest);
|
|
338
|
+
content = adaptFrameworkFile(content, file, provider);
|
|
236
339
|
}
|
|
340
|
+
writeFileSync(dest, content, 'utf-8');
|
|
237
341
|
}
|
|
238
342
|
|
|
239
343
|
// Copy each framework directory recursively
|
|
@@ -257,35 +361,36 @@ async function configureIDE(targetDir, ideKey, selectedMCPs, options = {}) {
|
|
|
257
361
|
|
|
258
362
|
if (ideKey === 'claude-code') {
|
|
259
363
|
// Thin router
|
|
260
|
-
const routerContent = `# /chati —
|
|
261
|
-
|
|
262
|
-
## CRITICAL — Language Override
|
|
364
|
+
const routerContent = `# /chati — Deterministic Router
|
|
263
365
|
|
|
264
|
-
|
|
265
|
-
ALL responses MUST be in this language. This overrides any global IDE language setting.
|
|
366
|
+
## STEP 0 — Run the deterministic pipeline router (MUST be first)
|
|
266
367
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
| \`es\` | Espanol |
|
|
272
|
-
| \`fr\` | Francais |
|
|
368
|
+
Run via Bash:
|
|
369
|
+
\`\`\`
|
|
370
|
+
node .chati.dev/orchestrator/chati-router.js
|
|
371
|
+
\`\`\`
|
|
273
372
|
|
|
274
|
-
|
|
373
|
+
Parse the JSON output. This single command already handled: license validation, integrity check, session state, pipeline routing, and team registration (if needed). You do NOT need to run any of these separately.
|
|
275
374
|
|
|
276
|
-
|
|
375
|
+
### If \`ok\` is false:
|
|
277
376
|
|
|
278
|
-
|
|
377
|
+
| \`error\` value | What to tell the user |
|
|
378
|
+
|---|---|
|
|
379
|
+
| \`license_invalid\` | Display \`license.reason\` as a friendly message. STOP. |
|
|
380
|
+
| \`license_guard_missing\` | "License enforcement is missing. Reinstall with: npx chati-dev init". STOP. |
|
|
381
|
+
| \`integrity_failed\` | "License enforcement was tampered with. Reinstall with: npx chati-dev init". STOP. |
|
|
382
|
+
| anything else | Display error. STOP. |
|
|
279
383
|
|
|
280
|
-
|
|
384
|
+
### If \`ok\` is true:
|
|
281
385
|
|
|
282
|
-
|
|
386
|
+
1. Use \`session.language\` for ALL responses (pt = Português, en = English, es = Español, fr = Français).
|
|
387
|
+
2. Read the full orchestrator at \`${orchestratorPath || '.chati.dev/orchestrator/chati.md'}\`.
|
|
388
|
+
3. The \`action\` field tells you what to do. Go to the matching "Action:" section in the orchestrator.
|
|
389
|
+
4. **If \`action\` is \`spawn_team\`**: the team is ALREADY registered (Step 1 already ran as code). The \`team\` object contains \`team_id\`, \`task_list_path\`, \`mailbox_path\`, \`members\`. Go directly to "Action: Spawn Team" Step 2 and use these values in the agent prompts. DO NOT run \`orchestrate spawn-team\` again — it already ran.
|
|
390
|
+
5. **If \`action\` is \`setup\`**: this is a new project. Follow "Action: Setup".
|
|
391
|
+
6. For any other action: follow the matching section in the orchestrator.
|
|
283
392
|
|
|
284
|
-
**
|
|
285
|
-
- \`.chati/session.yaml\` (session state — includes language)
|
|
286
|
-
- \`CLAUDE.local.md\` (runtime state — session lock, current agent)
|
|
287
|
-
- \`chati.dev/artifacts/handoffs/\` (latest handoff)
|
|
288
|
-
- \`chati.dev/config.yaml\` (version info)
|
|
393
|
+
**The \`pipeline\` object contains the full JSON from \`orchestrate next\`.** Use its fields (agent_file, spawn_command, handoff_status, etc.) as documented in the orchestrator.
|
|
289
394
|
|
|
290
395
|
**User input:** $ARGUMENTS
|
|
291
396
|
`;
|
|
@@ -334,7 +439,7 @@ Pass through all context: session state, handoffs, artifacts, and user input.
|
|
|
334
439
|
hooks: [
|
|
335
440
|
{
|
|
336
441
|
type: 'command',
|
|
337
|
-
command: 'node chati.dev/hooks/license-guard.js',
|
|
442
|
+
command: 'node .chati.dev/hooks/license-guard.js',
|
|
338
443
|
},
|
|
339
444
|
],
|
|
340
445
|
},
|
|
@@ -389,32 +494,31 @@ function generateProviderInstructions(providerName) {
|
|
|
389
494
|
# This file configures ${providerName} to work with Chati.dev
|
|
390
495
|
|
|
391
496
|
## System Location
|
|
392
|
-
All system content is in the
|
|
497
|
+
All system content is in the \`.chati.dev/\` directory (hidden). User artifacts are in \`artifacts/\`.
|
|
393
498
|
|
|
394
499
|
## Session State
|
|
395
500
|
Runtime session state is in \`.chati/session.yaml\` (IDE-agnostic).
|
|
396
501
|
|
|
397
502
|
## Getting Started
|
|
398
|
-
The orchestrator is at
|
|
399
|
-
Read it to understand routing, session management, and agent activation.
|
|
503
|
+
The orchestrator is at \`.chati.dev/orchestrator/chati.md\`.
|
|
400
504
|
|
|
401
505
|
## Constitution
|
|
402
|
-
Governance rules are in
|
|
506
|
+
Governance rules are in \`.chati.dev/constitution.md\`.
|
|
403
507
|
|
|
404
508
|
## Pipeline
|
|
405
509
|
\`\`\`
|
|
406
510
|
DISCOVER: WU -> Brief
|
|
407
|
-
PLAN: Detail
|
|
408
|
-
BUILD: Dev -> QA-
|
|
511
|
+
PLAN: [Planning Team: Detail + Architect + UX + QA-Planning] -> Phases -> Tasks
|
|
512
|
+
BUILD: [Build Team: Dev + QA-Implementation] -> QA-Visual
|
|
409
513
|
DEPLOY: DevOps
|
|
410
514
|
\`\`\`
|
|
411
515
|
|
|
412
516
|
## Agents
|
|
413
|
-
- DISCOVER: chati.dev/agents/discover/
|
|
414
|
-
- PLAN: chati.dev/agents/plan/
|
|
415
|
-
- Quality: chati.dev/agents/quality/
|
|
416
|
-
- BUILD: chati.dev/agents/build/
|
|
417
|
-
- DEPLOY: chati.dev/agents/deploy/
|
|
517
|
+
- DISCOVER: .chati.dev/agents/discover/
|
|
518
|
+
- PLAN: .chati.dev/agents/plan/
|
|
519
|
+
- Quality: .chati.dev/agents/quality/
|
|
520
|
+
- BUILD: .chati.dev/agents/build/
|
|
521
|
+
- DEPLOY: .chati.dev/agents/deploy/
|
|
418
522
|
`;
|
|
419
523
|
}
|
|
420
524
|
|
|
@@ -422,36 +526,165 @@ DEPLOY: DevOps
|
|
|
422
526
|
* Append chati.dev runtime entries to .gitignore.
|
|
423
527
|
* Session lock files are runtime-only and should never be committed.
|
|
424
528
|
*/
|
|
425
|
-
function updateGitignore(targetDir, selectedIDEs) {
|
|
426
|
-
const
|
|
529
|
+
export function updateGitignore(targetDir, selectedIDEs) {
|
|
530
|
+
const START_MARKER = '# BEGIN Chati.dev — do not edit between markers';
|
|
531
|
+
const END_MARKER = '# END Chati.dev';
|
|
532
|
+
// Legacy marker from pre-4.3.0 installs — detected for migration
|
|
533
|
+
const LEGACY_MARKER = '# Chati.dev runtime files';
|
|
534
|
+
|
|
535
|
+
const block = [
|
|
536
|
+
START_MARKER,
|
|
537
|
+
'# Framework files — generated by chati-dev, not part of your project.',
|
|
538
|
+
'# Do NOT remove these entries. If a framework file is already in git history,',
|
|
539
|
+
'# run: git rm -r --cached <path> after upgrading.',
|
|
540
|
+
'',
|
|
541
|
+
'# Framework (all code, prompts, hooks, orchestrator, data)',
|
|
542
|
+
'.chati.dev/',
|
|
427
543
|
'',
|
|
428
|
-
'#
|
|
544
|
+
'# Runtime state (session, teams, timeline, transient memories)',
|
|
545
|
+
'.chati/session.yaml',
|
|
546
|
+
'.chati/teams/',
|
|
547
|
+
'.chati/timeline.json',
|
|
429
548
|
'.chati/memories/*/session/',
|
|
549
|
+
'# NOTE: durable/ and daily/ memories ARE committed — they contain project knowledge',
|
|
550
|
+
'',
|
|
551
|
+
'# Claude Code integration (framework portions only)',
|
|
552
|
+
'.claude/commands/chati.md',
|
|
553
|
+
'.claude/rules/chati/',
|
|
554
|
+
'.claude/settings.local.json',
|
|
555
|
+
'# NOTE: .claude/settings.json IS committed — it is shared team config (hooks, MCPs)',
|
|
556
|
+
'',
|
|
557
|
+
'# Session locks (runtime, per-machine)',
|
|
558
|
+
'CLAUDE.local.md',
|
|
559
|
+
'.gemini/session-lock.md',
|
|
560
|
+
'AGENTS.override.md',
|
|
430
561
|
];
|
|
431
562
|
|
|
432
|
-
if (selectedIDEs.includes('claude-code')) {
|
|
433
|
-
entries.push('CLAUDE.local.md');
|
|
434
|
-
}
|
|
435
563
|
if (selectedIDEs.includes('gemini-cli')) {
|
|
436
|
-
|
|
564
|
+
block.push(
|
|
565
|
+
'',
|
|
566
|
+
'# Gemini provider framework files',
|
|
567
|
+
'.gemini/commands/',
|
|
568
|
+
'.gemini/context/',
|
|
569
|
+
'.gemini/hooks/',
|
|
570
|
+
'.gemini/settings.json',
|
|
571
|
+
);
|
|
437
572
|
}
|
|
573
|
+
|
|
438
574
|
if (selectedIDEs.includes('codex-cli')) {
|
|
439
|
-
|
|
575
|
+
block.push(
|
|
576
|
+
'',
|
|
577
|
+
'# Codex provider framework files',
|
|
578
|
+
'.codex/',
|
|
579
|
+
'.agents/skills/chati/',
|
|
580
|
+
);
|
|
440
581
|
}
|
|
441
582
|
|
|
442
|
-
|
|
583
|
+
block.push('', END_MARKER);
|
|
443
584
|
|
|
585
|
+
const newBlock = block.join('\n');
|
|
444
586
|
const gitignorePath = join(targetDir, '.gitignore');
|
|
445
|
-
const marker = '# Chati.dev runtime files';
|
|
446
587
|
|
|
447
|
-
if (existsSync(gitignorePath)) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
588
|
+
if (!existsSync(gitignorePath)) {
|
|
589
|
+
writeFileSync(gitignorePath, newBlock + '\n', 'utf-8');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
let existing = readFileSync(gitignorePath, 'utf-8');
|
|
594
|
+
|
|
595
|
+
// Strip new-style block if present (idempotency)
|
|
596
|
+
const newStartIdx = existing.indexOf(START_MARKER);
|
|
597
|
+
const newEndIdx = existing.indexOf(END_MARKER);
|
|
598
|
+
if (newStartIdx !== -1 && newEndIdx !== -1) {
|
|
599
|
+
existing = existing.slice(0, newStartIdx) + existing.slice(newEndIdx + END_MARKER.length);
|
|
600
|
+
existing = existing.replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Strip legacy block if present (pre-4.3.0)
|
|
604
|
+
if (existing.includes(LEGACY_MARKER)) {
|
|
605
|
+
const lines = existing.split('\n');
|
|
606
|
+
const legacyIdx = lines.findIndex(l => l.includes(LEGACY_MARKER));
|
|
607
|
+
if (legacyIdx !== -1) {
|
|
608
|
+
// Remove legacy marker line + the short known-legacy entries that followed
|
|
609
|
+
const legacyEntries = new Set([
|
|
610
|
+
'.chati/memories/*/session/',
|
|
611
|
+
'CLAUDE.local.md',
|
|
612
|
+
'.gemini/session-lock.md',
|
|
613
|
+
'AGENTS.override.md',
|
|
614
|
+
'',
|
|
615
|
+
]);
|
|
616
|
+
let endIdx = legacyIdx + 1;
|
|
617
|
+
while (endIdx < lines.length && legacyEntries.has(lines[endIdx].trim())) endIdx++;
|
|
618
|
+
lines.splice(legacyIdx, endIdx - legacyIdx);
|
|
619
|
+
existing = lines.join('\n').trimEnd();
|
|
620
|
+
}
|
|
454
621
|
}
|
|
622
|
+
|
|
623
|
+
writeFileSync(gitignorePath, existing.trimEnd() + '\n\n' + newBlock + '\n', 'utf-8');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Section-aware merge for CLAUDE.md: preserves user-authored sections and
|
|
628
|
+
* framework-managed sections (those with markers). Framework sections are
|
|
629
|
+
* re-rendered from the fresh template; everything else is kept as-is.
|
|
630
|
+
*/
|
|
631
|
+
function mergeClaudeMd(existing, fresh) {
|
|
632
|
+
// Framework-managed sections — these come from the install template AND from
|
|
633
|
+
// magic-docs runtime updates. Replace these from fresh template; preserve all others.
|
|
634
|
+
const frameworkSections = [
|
|
635
|
+
// Install template (templates.js generateClaudeMd)
|
|
636
|
+
'## Project Context',
|
|
637
|
+
'## Chati.dev',
|
|
638
|
+
// Magic-docs runtime sections (when project starts)
|
|
639
|
+
'## Current State',
|
|
640
|
+
'## Session Lock',
|
|
641
|
+
'## Recent Decisions',
|
|
642
|
+
'## Framework',
|
|
643
|
+
'## How to Resume',
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
// If existing has no framework sections at all, user has a custom CLAUDE.md;
|
|
647
|
+
// just append the framework template separator + fresh template below.
|
|
648
|
+
const hasAnyFwSection = frameworkSections.some(h => existing.includes(h));
|
|
649
|
+
if (!hasAnyFwSection) {
|
|
650
|
+
return existing.trimEnd() + '\n\n<!-- Chati.dev framework context (auto-managed) -->\n\n' + fresh;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Otherwise, do section-level replacement — for each framework section in fresh,
|
|
654
|
+
// replace the corresponding section in existing (or append if missing).
|
|
655
|
+
let merged = existing;
|
|
656
|
+
for (const header of frameworkSections) {
|
|
657
|
+
const freshSection = extractSection(fresh, header);
|
|
658
|
+
if (!freshSection) continue;
|
|
659
|
+
if (merged.includes(header)) {
|
|
660
|
+
merged = replaceSection(merged, header, freshSection);
|
|
661
|
+
} else {
|
|
662
|
+
merged = merged.trimEnd() + '\n\n' + freshSection;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return merged;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function extractSection(content, header) {
|
|
669
|
+
const lines = content.split('\n');
|
|
670
|
+
const startIdx = lines.findIndex(l => l.trim() === header);
|
|
671
|
+
if (startIdx === -1) return null;
|
|
672
|
+
let endIdx = startIdx + 1;
|
|
673
|
+
while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
|
|
674
|
+
return lines.slice(startIdx, endIdx).join('\n');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function replaceSection(content, header, newSection) {
|
|
678
|
+
const lines = content.split('\n');
|
|
679
|
+
const startIdx = lines.findIndex(l => l.trim() === header);
|
|
680
|
+
if (startIdx === -1) return content;
|
|
681
|
+
let endIdx = startIdx + 1;
|
|
682
|
+
while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
|
|
683
|
+
return [
|
|
684
|
+
...lines.slice(0, startIdx),
|
|
685
|
+
newSection,
|
|
686
|
+
...lines.slice(endIdx),
|
|
687
|
+
].join('\n');
|
|
455
688
|
}
|
|
456
689
|
|
|
457
690
|
/**
|
|
@@ -463,6 +696,114 @@ function createDir(dir) {
|
|
|
463
696
|
}
|
|
464
697
|
}
|
|
465
698
|
|
|
699
|
+
/**
|
|
700
|
+
* Recursively copy CLI source (excluding tests, node_modules, hidden files).
|
|
701
|
+
* Used to bundle the CLI source into the user's .chati.dev/_cli/ folder so
|
|
702
|
+
* the framework runs self-contained (no `npx chati-dev` dependency).
|
|
703
|
+
*/
|
|
704
|
+
function copyCliSource(srcDir, destDir) {
|
|
705
|
+
if (!existsSync(srcDir)) return;
|
|
706
|
+
createDir(destDir);
|
|
707
|
+
const entries = readdirSync(srcDir);
|
|
708
|
+
for (const entry of entries) {
|
|
709
|
+
if (entry.startsWith('.')) continue;
|
|
710
|
+
if (entry === 'node_modules' || entry === 'test' || entry === 'tests') continue;
|
|
711
|
+
const srcPath = join(srcDir, entry);
|
|
712
|
+
const destPath = join(destDir, entry);
|
|
713
|
+
let stat;
|
|
714
|
+
try { stat = statSync(srcPath); } catch { continue; }
|
|
715
|
+
if (stat.isDirectory()) {
|
|
716
|
+
copyCliSource(srcPath, destPath);
|
|
717
|
+
} else {
|
|
718
|
+
copyFileSync(srcPath, destPath);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Recursively copy a directory verbatim (used for bundling node_modules trees).
|
|
725
|
+
*/
|
|
726
|
+
function copyDirVerbatim(srcDir, destDir) {
|
|
727
|
+
if (!existsSync(srcDir)) return;
|
|
728
|
+
createDir(destDir);
|
|
729
|
+
for (const entry of readdirSync(srcDir)) {
|
|
730
|
+
const srcPath = join(srcDir, entry);
|
|
731
|
+
const destPath = join(destDir, entry);
|
|
732
|
+
let stat;
|
|
733
|
+
try { stat = statSync(srcPath); } catch { continue; }
|
|
734
|
+
if (stat.isDirectory()) {
|
|
735
|
+
copyDirVerbatim(srcPath, destPath);
|
|
736
|
+
} else {
|
|
737
|
+
try { copyFileSync(srcPath, destPath); } catch { /* skip unreadable */ }
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Bundle chati-dev's production dependencies into .chati.dev/_cli/node_modules/
|
|
744
|
+
* so the bundled CLI can import js-yaml, chalk, etc. without relying on a
|
|
745
|
+
* project-level node_modules tree.
|
|
746
|
+
*
|
|
747
|
+
* Uses createRequire to resolve each dep from the installed chati-dev package,
|
|
748
|
+
* which works whether the package is hoisted or nested in the user's
|
|
749
|
+
* node_modules (npx cache, npm install, etc.).
|
|
750
|
+
*/
|
|
751
|
+
function copyCliDependencies(pkgDir, destNodeModules) {
|
|
752
|
+
let pkg;
|
|
753
|
+
try {
|
|
754
|
+
pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
|
|
755
|
+
} catch { return; }
|
|
756
|
+
const deps = [
|
|
757
|
+
...Object.keys(pkg.dependencies || {}),
|
|
758
|
+
...Object.keys(pkg.optionalDependencies || {}),
|
|
759
|
+
];
|
|
760
|
+
if (deps.length === 0) return;
|
|
761
|
+
|
|
762
|
+
// Resolve each dep from the package.json location (works for hoisted + nested layouts).
|
|
763
|
+
// eslint-disable-next-line no-undef
|
|
764
|
+
const require = createRequire(join(pkgDir, 'package.json'));
|
|
765
|
+
createDir(destNodeModules);
|
|
766
|
+
|
|
767
|
+
const visited = new Set();
|
|
768
|
+
const queue = [...deps];
|
|
769
|
+
|
|
770
|
+
while (queue.length > 0) {
|
|
771
|
+
const depName = queue.shift();
|
|
772
|
+
if (visited.has(depName)) continue;
|
|
773
|
+
visited.add(depName);
|
|
774
|
+
|
|
775
|
+
let depDir;
|
|
776
|
+
try {
|
|
777
|
+
// Try direct package.json resolve first (fast path)
|
|
778
|
+
depDir = dirname(require.resolve(`${depName}/package.json`));
|
|
779
|
+
} catch {
|
|
780
|
+
// Fallback: resolve the main entry then walk up to find package root.
|
|
781
|
+
// Needed for packages with strict "exports" (chalk, ora, etc.).
|
|
782
|
+
try {
|
|
783
|
+
let p = require.resolve(depName);
|
|
784
|
+
while (p && p !== dirname(p)) {
|
|
785
|
+
p = dirname(p);
|
|
786
|
+
if (existsSync(join(p, 'package.json')) && basename(p) === depName.split('/').pop()) {
|
|
787
|
+
depDir = p;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
} catch { continue; }
|
|
792
|
+
if (!depDir) continue;
|
|
793
|
+
}
|
|
794
|
+
const depPkgJsonPath = join(depDir, 'package.json');
|
|
795
|
+
copyDirVerbatim(depDir, join(destNodeModules, depName));
|
|
796
|
+
|
|
797
|
+
// Include transitive deps
|
|
798
|
+
try {
|
|
799
|
+
const depPkg = JSON.parse(readFileSync(depPkgJsonPath, 'utf-8'));
|
|
800
|
+
for (const t of Object.keys(depPkg.dependencies || {})) {
|
|
801
|
+
if (!visited.has(t)) queue.push(t);
|
|
802
|
+
}
|
|
803
|
+
} catch { /* continue */ }
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
466
807
|
/**
|
|
467
808
|
* Write .claude/settings.json with safe merge against existing user content.
|
|
468
809
|
*
|