chati-dev 4.2.1 → 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 +8 -7
- package/framework/constitution.md +127 -43
- 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 +91 -36
- package/framework/hooks/license-guard.js +129 -190
- package/framework/hooks/mode-governance.js +68 -16
- package/framework/hooks/model-governance.js +19 -8
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/post-dev.js +207 -0
- package/framework/hooks/prism-engine.js +288 -105
- package/framework/hooks/read-protection.js +37 -9
- package/framework/hooks/reasoning-escalator.js +371 -0
- package/framework/hooks/reference-trigger.js +117 -0
- package/framework/hooks/session-digest.js +94 -20
- package/framework/hooks/settings.json +32 -1
- package/framework/hooks/style-guard.js +52 -13
- package/framework/hooks/team-quality-gate.js +58 -25
- package/framework/hooks/undercover-guard.js +34 -13
- 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 +318 -115
- 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 +226 -0
- package/src/config/context-file-generator.js +41 -21
- package/src/config/framework-adapter.js +1 -0
- package/src/config/gemini-hooks-generator.js +76 -6
- 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 +557 -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 +52 -31
- 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 +1908 -80
- 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 -367
- 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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
13
14
|
import { runPrism } from '../context/engine.js';
|
|
14
15
|
import { loadHandoff, formatHandoff } from '../tasks/handoff.js';
|
|
15
16
|
import { getWriteScope } from './isolation.js';
|
|
@@ -24,23 +25,41 @@ import { PROVIDER_LIMITS as PROVIDER_TOKEN_LIMITS } from '../utils/provider-limi
|
|
|
24
25
|
import { AGENT_MODELS } from '../../framework/hooks/model-governance.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
+
* Agent definition file paths relative to framework directory.
|
|
29
|
+
* Used internally — callers should use getAgentFile(agent, projectDir).
|
|
30
|
+
* @private
|
|
28
31
|
*/
|
|
29
|
-
|
|
30
|
-
'greenfield-wu': '
|
|
31
|
-
'brownfield-wu': '
|
|
32
|
-
brief: '
|
|
33
|
-
detail: '
|
|
34
|
-
architect: '
|
|
35
|
-
ux: '
|
|
36
|
-
phases: '
|
|
37
|
-
tasks: '
|
|
38
|
-
'qa-planning': '
|
|
39
|
-
dev: '
|
|
40
|
-
'qa-implementation': '
|
|
41
|
-
|
|
32
|
+
const AGENT_FILE_PATHS = {
|
|
33
|
+
'greenfield-wu': 'agents/discover/greenfield-wu.md',
|
|
34
|
+
'brownfield-wu': 'agents/discover/brownfield-wu.md',
|
|
35
|
+
brief: 'agents/discover/brief.md',
|
|
36
|
+
detail: 'agents/plan/detail.md',
|
|
37
|
+
architect: 'agents/plan/architect.md',
|
|
38
|
+
ux: 'agents/plan/ux.md',
|
|
39
|
+
phases: 'agents/plan/phases.md',
|
|
40
|
+
tasks: 'agents/plan/tasks.md',
|
|
41
|
+
'qa-planning': 'agents/quality/qa-planning.md',
|
|
42
|
+
dev: 'agents/build/dev.md',
|
|
43
|
+
'qa-implementation': 'agents/quality/qa-implementation.md',
|
|
44
|
+
'qa-visual': 'agents/quality/qa-visual.md',
|
|
45
|
+
devops: 'agents/deploy/devops.md',
|
|
42
46
|
};
|
|
43
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Resolve an agent's definition file path relative to the project root.
|
|
50
|
+
* Handles both .chati.dev/ (new) and chati.dev/ (legacy) structures.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} agentName - Agent name (e.g. 'detail')
|
|
53
|
+
* @param {string} projectDir - Project root directory
|
|
54
|
+
* @returns {string|null} Relative path from project root, or null if unknown
|
|
55
|
+
*/
|
|
56
|
+
export function getAgentFile(agentName, projectDir) {
|
|
57
|
+
const rel = AGENT_FILE_PATHS[agentName];
|
|
58
|
+
if (!rel) return null;
|
|
59
|
+
return `${resolveFrameworkDir(projectDir)}/${rel}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
44
63
|
/**
|
|
45
64
|
* 3-Tier Tool Mesh — Token-aware tool loading profiles per agent.
|
|
46
65
|
*
|
|
@@ -63,6 +82,7 @@ export const TOOL_PROFILES = {
|
|
|
63
82
|
'qa-planning': { T1: ['Read', 'Glob', 'Grep'], T2: [], T3: [] },
|
|
64
83
|
dev: { T1: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'], T2: ['WebSearch', 'WebFetch', 'Task'], T3: ['playwright'] },
|
|
65
84
|
'qa-implementation': { T1: ['Read', 'Bash', 'Glob', 'Grep'], T2: ['Task'], T3: [] },
|
|
85
|
+
'qa-visual': { T1: ['Read', 'Bash', 'Glob', 'Grep'], T2: ['Write'], T3: [] },
|
|
66
86
|
devops: { T1: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'], T2: ['WebSearch'], T3: ['docker'] },
|
|
67
87
|
};
|
|
68
88
|
|
|
@@ -220,7 +240,7 @@ export function buildAgentPrompt(config) {
|
|
|
220
240
|
* Spawned terminals always start FRESH (remainingPercent: 100).
|
|
221
241
|
*/
|
|
222
242
|
function buildPrismSection(config) {
|
|
223
|
-
const domainsDir = join(config.projectDir,
|
|
243
|
+
const domainsDir = join(config.projectDir, resolveFrameworkDir(config.projectDir), 'domains');
|
|
224
244
|
|
|
225
245
|
if (!existsSync(domainsDir)) {
|
|
226
246
|
return { xml: null, layerCount: 0 };
|
|
@@ -262,17 +282,14 @@ function buildPrismSection(config) {
|
|
|
262
282
|
* For non-primary providers, resolves via overlay directory.
|
|
263
283
|
*/
|
|
264
284
|
function loadAgentDefinition(agent, projectDir, provider = null) {
|
|
265
|
-
const
|
|
266
|
-
if (!
|
|
267
|
-
|
|
268
|
-
// Strip 'chati.dev/' prefix for overlay resolution
|
|
269
|
-
const relToFramework = relativePath.replace(/^chati\.dev\//, '');
|
|
285
|
+
const relToFramework = AGENT_FILE_PATHS[agent];
|
|
286
|
+
if (!relToFramework) return null;
|
|
270
287
|
|
|
271
288
|
let fullPath;
|
|
272
289
|
if (provider && provider !== 'claude') {
|
|
273
290
|
fullPath = resolveOverlayPath(projectDir, relToFramework, provider);
|
|
274
291
|
} else {
|
|
275
|
-
fullPath = join(projectDir,
|
|
292
|
+
fullPath = join(projectDir, resolveFrameworkDir(projectDir), relToFramework);
|
|
276
293
|
}
|
|
277
294
|
|
|
278
295
|
if (!existsSync(fullPath)) return null;
|
|
@@ -317,7 +334,7 @@ function buildHandoffSection(config) {
|
|
|
317
334
|
* respected by the spawned Claude process.
|
|
318
335
|
*/
|
|
319
336
|
function buildWriteScopeSection(config) {
|
|
320
|
-
const scope = config.writeScope || getWriteScope(config.agent);
|
|
337
|
+
const scope = config.writeScope || getWriteScope(config.agent, config.projectDir);
|
|
321
338
|
|
|
322
339
|
if (scope.length === 0) {
|
|
323
340
|
return (
|
|
@@ -502,9 +519,9 @@ export function loadPreset(stack, projectDir) {
|
|
|
502
519
|
return { loaded: false, preset: null, stack: stack || '' };
|
|
503
520
|
}
|
|
504
521
|
|
|
505
|
-
// Check both deployed (chati.dev/presets/) and package (framework/presets/) locations
|
|
522
|
+
// Check both deployed (chati.dev/presets/ or .chati.dev/presets/) and package (framework/presets/) locations
|
|
506
523
|
const locations = [
|
|
507
|
-
join(projectDir,
|
|
524
|
+
join(projectDir, resolveFrameworkDir(projectDir), 'presets'),
|
|
508
525
|
join(projectDir, 'framework', 'presets'),
|
|
509
526
|
];
|
|
510
527
|
|
|
@@ -535,7 +552,7 @@ export function detectPreset(projectDir) {
|
|
|
535
552
|
|
|
536
553
|
// Check both deployed and package locations
|
|
537
554
|
const locations = [
|
|
538
|
-
join(projectDir,
|
|
555
|
+
join(projectDir, resolveFrameworkDir(projectDir), 'presets'),
|
|
539
556
|
join(projectDir, 'framework', 'presets'),
|
|
540
557
|
];
|
|
541
558
|
|
|
@@ -210,7 +210,7 @@ async function main() {
|
|
|
210
210
|
summary: r.handoff?.summary || '',
|
|
211
211
|
}));
|
|
212
212
|
|
|
213
|
-
const merged = mergeHandoffs(mergeInput);
|
|
213
|
+
const merged = mergeHandoffs(mergeInput, projectDir);
|
|
214
214
|
const nextAgent = determineNextAgent(agents);
|
|
215
215
|
const consolidated = buildConsolidatedHandoff(merged, nextAgent);
|
|
216
216
|
|
package/src/terminal/run-team.js
CHANGED
|
@@ -36,13 +36,13 @@ import {
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
37
|
|
|
38
38
|
const TEAM_MEMBERS = {
|
|
39
|
-
planning: ['detail', 'architect', 'ux'],
|
|
39
|
+
planning: ['detail', 'architect', 'ux', 'qa-planning'],
|
|
40
40
|
build: ['dev', 'qa-implementation'],
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
// Task IDs match the Shared Task List template (team-planning-tasks.yaml / team-build-tasks.yaml)
|
|
44
44
|
const TEAM_TASK_IDS = {
|
|
45
|
-
planning: ['TT-PLN-001', 'TT-PLN-002', 'TT-PLN-003'],
|
|
45
|
+
planning: ['TT-PLN-001', 'TT-PLN-002', 'TT-PLN-003', 'TT-PLN-005'],
|
|
46
46
|
build: ['TT-BLD-001-DEV', 'TT-BLD-001-QA'],
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -276,7 +276,7 @@ async function main() {
|
|
|
276
276
|
summary: r.handoff?.summary || '',
|
|
277
277
|
}));
|
|
278
278
|
|
|
279
|
-
const merged = mergeHandoffs(mergeInput);
|
|
279
|
+
const merged = mergeHandoffs(mergeInput, projectDir);
|
|
280
280
|
const nextAgent = determineNextAgent(teamType);
|
|
281
281
|
const consolidated = buildConsolidatedHandoff(merged, nextAgent);
|
|
282
282
|
|
|
@@ -8,11 +8,35 @@
|
|
|
8
8
|
* when multiple team members update concurrently.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
|
|
12
|
-
import { join, dirname } from 'path';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, realpathSync } from 'fs';
|
|
12
|
+
import { join, dirname, resolve, relative } from 'path';
|
|
13
13
|
import yaml from 'js-yaml';
|
|
14
14
|
import { withLock } from '../utils/file-lock.js';
|
|
15
15
|
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Path Validation
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Assert that a target path is within .chati/teams/ to prevent path traversal.
|
|
22
|
+
* @param {string} targetPath - Absolute or relative path to validate
|
|
23
|
+
* @param {string} projectDir - Project root directory
|
|
24
|
+
* @throws {Error} If targetPath resolves outside .chati/teams/
|
|
25
|
+
*/
|
|
26
|
+
function assertWithinTeamsDir(targetPath, projectDir) {
|
|
27
|
+
const teamsRoot = resolve(projectDir, '.chati', 'teams');
|
|
28
|
+
// Resolve symlinks so a symlink inside teams/ pointing outside (e.g., /etc/passwd)
|
|
29
|
+
// cannot bypass the boundary check. Fall back to plain resolve() if file doesn't exist
|
|
30
|
+
// yet (which is normal — we're about to create it).
|
|
31
|
+
const safeRealpath = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
|
|
32
|
+
const realTeamsRoot = safeRealpath(teamsRoot);
|
|
33
|
+
const absPath = safeRealpath(targetPath);
|
|
34
|
+
const rel = relative(realTeamsRoot, absPath);
|
|
35
|
+
if (rel.startsWith('..') || rel.startsWith('/')) {
|
|
36
|
+
throw new Error(`Path ${targetPath} is outside .chati/teams/ — refusing write`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
16
40
|
// ---------------------------------------------------------------------------
|
|
17
41
|
// Task List Management
|
|
18
42
|
// ---------------------------------------------------------------------------
|
|
@@ -25,6 +49,9 @@ import { withLock } from '../utils/file-lock.js';
|
|
|
25
49
|
* @returns {{ created: boolean, path: string }}
|
|
26
50
|
*/
|
|
27
51
|
export function initTaskList(destPath, templatePath, overrides = {}) {
|
|
52
|
+
// Derive projectDir: destPath is <projectDir>/.chati/teams/<teamId>/<file>
|
|
53
|
+
const projectDir = resolve(destPath, '..', '..', '..', '..');
|
|
54
|
+
assertWithinTeamsDir(destPath, projectDir);
|
|
28
55
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
29
56
|
|
|
30
57
|
if (!existsSync(templatePath)) {
|
|
@@ -61,6 +88,9 @@ export function readTaskList(path) {
|
|
|
61
88
|
* @returns {Promise<{ updated: boolean, task: object|null }>}
|
|
62
89
|
*/
|
|
63
90
|
export async function updateTaskStatus(path, taskId, updates) {
|
|
91
|
+
// Derive projectDir: path is <projectDir>/.chati/teams/<teamId>/<file>
|
|
92
|
+
const projectDir = resolve(path, '..', '..', '..', '..');
|
|
93
|
+
assertWithinTeamsDir(path, projectDir);
|
|
64
94
|
return withLock(path, () => {
|
|
65
95
|
const taskList = readTaskList(path);
|
|
66
96
|
if (!taskList || !taskList.tasks) return { updated: false, task: null };
|
|
@@ -68,6 +98,8 @@ export async function updateTaskStatus(path, taskId, updates) {
|
|
|
68
98
|
const task = taskList.tasks.find(t => t.id === taskId);
|
|
69
99
|
if (!task) return { updated: false, task: null };
|
|
70
100
|
|
|
101
|
+
// Normalize 'completed' → 'done' (CLI uses 'completed', team YAML uses 'done')
|
|
102
|
+
if (updates.status === 'completed') updates.status = 'done';
|
|
71
103
|
Object.assign(task, updates, { last_updated: new Date().toISOString() });
|
|
72
104
|
if (updates.status === 'done' && !task.completed_at) {
|
|
73
105
|
task.completed_at = new Date().toISOString();
|
|
@@ -93,6 +125,10 @@ export function getTeamProgress(path) {
|
|
|
93
125
|
const tasks = taskList.tasks;
|
|
94
126
|
const memberProgress = {};
|
|
95
127
|
|
|
128
|
+
// Normalize: both 'done' and 'completed' indicate task completion.
|
|
129
|
+
// CLI uses AGENT_STATUS.COMPLETED = 'completed'; team task YAML traditionally uses 'done'.
|
|
130
|
+
const isDone = (status) => status === 'done' || status === 'completed';
|
|
131
|
+
|
|
96
132
|
for (const task of tasks) {
|
|
97
133
|
const assignee = task.assigned_to;
|
|
98
134
|
if (!assignee) continue;
|
|
@@ -100,7 +136,7 @@ export function getTeamProgress(path) {
|
|
|
100
136
|
memberProgress[assignee] = { status: 'pending', score: null, tasks: 0, completed: 0 };
|
|
101
137
|
}
|
|
102
138
|
memberProgress[assignee].tasks++;
|
|
103
|
-
if (task.status
|
|
139
|
+
if (isDone(task.status)) {
|
|
104
140
|
memberProgress[assignee].completed++;
|
|
105
141
|
memberProgress[assignee].score = task.score;
|
|
106
142
|
} else if (task.status === 'in_progress') {
|
|
@@ -119,7 +155,7 @@ export function getTeamProgress(path) {
|
|
|
119
155
|
|
|
120
156
|
return {
|
|
121
157
|
total: tasks.length,
|
|
122
|
-
done: tasks.filter(t => t.status
|
|
158
|
+
done: tasks.filter(t => isDone(t.status)).length,
|
|
123
159
|
pending: tasks.filter(t => t.status === 'pending').length,
|
|
124
160
|
blocked: tasks.filter(t => t.status === 'blocked').length,
|
|
125
161
|
inProgress: tasks.filter(t => t.status === 'in_progress').length,
|
|
@@ -142,6 +178,9 @@ export function getTeamProgress(path) {
|
|
|
142
178
|
* @returns {{ written: boolean, path: string }}
|
|
143
179
|
*/
|
|
144
180
|
export function writeMailboxMessage(mailboxDir, from, to, type, payload) {
|
|
181
|
+
// Derive projectDir: mailboxDir is <projectDir>/.chati/teams/<teamId>/mailbox
|
|
182
|
+
const projectDir = resolve(mailboxDir, '..', '..', '..', '..');
|
|
183
|
+
assertWithinTeamsDir(mailboxDir, projectDir);
|
|
145
184
|
mkdirSync(mailboxDir, { recursive: true });
|
|
146
185
|
|
|
147
186
|
const timestamp = Date.now();
|
package/src/upgrade/backup.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync, rmSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Create backup of chati.dev/ directory before upgrade
|
|
6
7
|
* Backup location: chati.dev/.backup-v{version}/
|
|
7
8
|
*/
|
|
8
9
|
export function createBackup(targetDir, currentVersion) {
|
|
9
|
-
const frameworkDir = join(targetDir,
|
|
10
|
+
const frameworkDir = join(targetDir, resolveFrameworkDir(targetDir));
|
|
10
11
|
const backupDir = join(frameworkDir, `.backup-v${currentVersion}`);
|
|
11
12
|
|
|
12
13
|
if (existsSync(backupDir)) {
|
|
@@ -60,7 +61,7 @@ export function createBackup(targetDir, currentVersion) {
|
|
|
60
61
|
* Restore from backup (rollback)
|
|
61
62
|
*/
|
|
62
63
|
export function restoreFromBackup(targetDir, version) {
|
|
63
|
-
const frameworkDir = join(targetDir,
|
|
64
|
+
const frameworkDir = join(targetDir, resolveFrameworkDir(targetDir));
|
|
64
65
|
const backupDir = join(frameworkDir, `.backup-v${version}`);
|
|
65
66
|
|
|
66
67
|
if (!existsSync(backupDir)) {
|
package/src/upgrade/checker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
3
4
|
import yaml from 'js-yaml';
|
|
4
5
|
import semver from 'semver';
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ import semver from 'semver';
|
|
|
7
8
|
* Read current installed version from config.yaml
|
|
8
9
|
*/
|
|
9
10
|
export function getCurrentVersion(targetDir) {
|
|
10
|
-
const configPath = join(targetDir,
|
|
11
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
11
12
|
if (!existsSync(configPath)) return null;
|
|
12
13
|
|
|
13
14
|
try {
|
|
@@ -74,7 +75,7 @@ function getChangelog(fromVersion, toVersion) {
|
|
|
74
75
|
* Update config.yaml with the new version after successful upgrade
|
|
75
76
|
*/
|
|
76
77
|
export function updateConfigVersion(targetDir, newVersion) {
|
|
77
|
-
const configPath = join(targetDir,
|
|
78
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
78
79
|
if (!existsSync(configPath)) return false;
|
|
79
80
|
|
|
80
81
|
try {
|
package/src/upgrade/migrator.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
3
5
|
import yaml from 'js-yaml';
|
|
4
6
|
import semver from 'semver';
|
|
5
7
|
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
// Bundled framework source — always contains the latest migrations from the CLI package
|
|
10
|
+
const BUNDLED_MIGRATIONS = join(__dirname, '..', '..', 'framework', 'migrations');
|
|
11
|
+
const MONOREPO_MIGRATIONS = join(__dirname, '..', '..', '..', '..', 'chati.dev', 'migrations');
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
|
-
* Find applicable migrations between two versions
|
|
14
|
+
* Find applicable migrations between two versions.
|
|
15
|
+
*
|
|
16
|
+
* Reads from the BUNDLED framework (shipped with the CLI package), NOT from
|
|
17
|
+
* the installed copy. This is critical because on upgrade, the installed
|
|
18
|
+
* framework still holds the OLD migrations — new ones aren't copied until
|
|
19
|
+
* after migrations run. Without this, new migrations would never execute on
|
|
20
|
+
* the first upgrade to the version that introduced them.
|
|
8
21
|
*/
|
|
9
22
|
export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
// Prefer bundled (npm package) source; fall back to monorepo dev layout;
|
|
24
|
+
// last resort, the already-installed copy (legacy behavior).
|
|
25
|
+
let migrationsDir = null;
|
|
26
|
+
if (existsSync(BUNDLED_MIGRATIONS)) {
|
|
27
|
+
migrationsDir = BUNDLED_MIGRATIONS;
|
|
28
|
+
} else if (existsSync(MONOREPO_MIGRATIONS)) {
|
|
29
|
+
migrationsDir = MONOREPO_MIGRATIONS;
|
|
30
|
+
} else {
|
|
31
|
+
const installedDir = join(targetDir, resolveFrameworkDir(targetDir), 'migrations');
|
|
32
|
+
if (existsSync(installedDir)) migrationsDir = installedDir;
|
|
33
|
+
}
|
|
34
|
+
if (!migrationsDir) return [];
|
|
12
35
|
|
|
13
36
|
const files = readdirSync(migrationsDir)
|
|
14
37
|
.filter(f => f.endsWith('.yaml'))
|
|
@@ -52,7 +75,7 @@ export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
|
52
75
|
/**
|
|
53
76
|
* Execute a single migration step
|
|
54
77
|
*/
|
|
55
|
-
function executeMigrationStep(targetDir, step) {
|
|
78
|
+
async function executeMigrationStep(targetDir, step) {
|
|
56
79
|
switch (step.type) {
|
|
57
80
|
case 'create_directory': {
|
|
58
81
|
const dir = join(targetDir, step.path);
|
|
@@ -117,6 +140,41 @@ function executeMigrationStep(targetDir, step) {
|
|
|
117
140
|
break;
|
|
118
141
|
}
|
|
119
142
|
|
|
143
|
+
case 'update_gitignore': {
|
|
144
|
+
// Delegates to installer's updateGitignore via a dynamic import to keep
|
|
145
|
+
// concerns separated. The selectedIDEs come from the installed config.
|
|
146
|
+
// Best-effort: if anything fails, we skip silently so migration doesn't break.
|
|
147
|
+
try {
|
|
148
|
+
const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
|
|
149
|
+
let selectedIDEs = ['claude-code'];
|
|
150
|
+
if (existsSync(configPath)) {
|
|
151
|
+
const cfg = yaml.load(readFileSync(configPath, 'utf-8')) || {};
|
|
152
|
+
if (Array.isArray(cfg.ides)) selectedIDEs = cfg.ides;
|
|
153
|
+
}
|
|
154
|
+
// Inline call (avoid circular import with core.js in tests).
|
|
155
|
+
// We replicate the updateGitignore logic here is not ideal, so instead
|
|
156
|
+
// call through a dedicated helper: core.js exports updateGitignore.
|
|
157
|
+
const core = await import('../installer/core.js');
|
|
158
|
+
if (typeof core.updateGitignore === 'function') {
|
|
159
|
+
core.updateGitignore(targetDir, selectedIDEs);
|
|
160
|
+
}
|
|
161
|
+
} catch { /* expected: skip if installer helper not available */ }
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'warn_tracked_files': {
|
|
166
|
+
// Non-blocking warning — detects already-tracked framework files.
|
|
167
|
+
try {
|
|
168
|
+
const { detectTrackedFrameworkFiles } = await import('./tracked-files-detector.js');
|
|
169
|
+
const { renderTrackedFilesWarning } = await import('./user-messages.js');
|
|
170
|
+
const tracked = detectTrackedFrameworkFiles(targetDir);
|
|
171
|
+
if (tracked.length > 0) {
|
|
172
|
+
process.stdout.write(renderTrackedFilesWarning(tracked));
|
|
173
|
+
}
|
|
174
|
+
} catch { /* expected: skip if git unavailable */ }
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
120
178
|
default:
|
|
121
179
|
// Unknown step type - skip
|
|
122
180
|
break;
|
|
@@ -138,7 +196,7 @@ export async function runMigrations(targetDir, fromVersion, toVersion) {
|
|
|
138
196
|
for (const migration of migrations) {
|
|
139
197
|
try {
|
|
140
198
|
for (const step of migration.steps) {
|
|
141
|
-
executeMigrationStep(targetDir, step);
|
|
199
|
+
await executeMigrationStep(targetDir, step);
|
|
142
200
|
}
|
|
143
201
|
results.push({ file: migration.file, success: true });
|
|
144
202
|
} catch (err) {
|
|
@@ -147,7 +205,7 @@ export async function runMigrations(targetDir, fromVersion, toVersion) {
|
|
|
147
205
|
// Attempt rollback for this migration
|
|
148
206
|
try {
|
|
149
207
|
for (const step of migration.rollback) {
|
|
150
|
-
executeMigrationStep(targetDir, step);
|
|
208
|
+
await executeMigrationStep(targetDir, step);
|
|
151
209
|
}
|
|
152
210
|
} catch (rollbackErr) {
|
|
153
211
|
console.error('[chati] migration rollback failed:', rollbackErr?.message);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Framework file patterns that should never be committed to git.
|
|
7
|
+
* Each entry is a path (relative to targetDir) that git ls-files will check.
|
|
8
|
+
*/
|
|
9
|
+
const FRAMEWORK_PATTERNS = [
|
|
10
|
+
'.chati.dev/',
|
|
11
|
+
'.chati/session.yaml',
|
|
12
|
+
'.chati/teams/',
|
|
13
|
+
'.chati/timeline.json',
|
|
14
|
+
'.chati/memories/shared/session/',
|
|
15
|
+
'.chati/memories/shared/gotchas.json',
|
|
16
|
+
'.chati/memories/shared/error-log.json',
|
|
17
|
+
'.claude/commands/chati.md',
|
|
18
|
+
'.claude/rules/chati/',
|
|
19
|
+
'.claude/settings.local.json',
|
|
20
|
+
'CLAUDE.local.md',
|
|
21
|
+
'.gemini/session-lock.md',
|
|
22
|
+
'AGENTS.override.md',
|
|
23
|
+
'.gemini/commands/',
|
|
24
|
+
'.gemini/context/',
|
|
25
|
+
'.gemini/hooks/',
|
|
26
|
+
'.gemini/settings.json',
|
|
27
|
+
'.codex/',
|
|
28
|
+
'.agents/skills/chati/',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check whether targetDir is a git repository by verifying .git/ exists.
|
|
33
|
+
* @param {string} targetDir
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function isGitRepo(targetDir) {
|
|
37
|
+
return existsSync(join(targetDir, '.git'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Detect which framework-owned paths are currently tracked by git.
|
|
42
|
+
* Does NOT modify git state — read-only operation.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} targetDir - absolute path to the project root
|
|
45
|
+
* @returns {string[]} array of tracked framework file paths (relative to targetDir)
|
|
46
|
+
*/
|
|
47
|
+
export function detectTrackedFrameworkFiles(targetDir) {
|
|
48
|
+
if (!isGitRepo(targetDir)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tracked = [];
|
|
53
|
+
|
|
54
|
+
for (const pattern of FRAMEWORK_PATTERNS) {
|
|
55
|
+
try {
|
|
56
|
+
const result = execSync(`git ls-files -- "${pattern}"`, {
|
|
57
|
+
cwd: targetDir,
|
|
58
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
})
|
|
61
|
+
.toString()
|
|
62
|
+
.trim();
|
|
63
|
+
|
|
64
|
+
if (result.length > 0) {
|
|
65
|
+
tracked.push(pattern);
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
// git ls-files failure means path is not tracked — skip
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Deduplicate in case patterns overlap
|
|
73
|
+
return [...new Set(tracked)];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the list of `git rm -r --cached <path>` commands for untracking.
|
|
78
|
+
* Deduplicates entries and returns one command string per path.
|
|
79
|
+
*
|
|
80
|
+
* @param {string[]} files - array of tracked framework paths
|
|
81
|
+
* @returns {string[]} array of git rm commands
|
|
82
|
+
*/
|
|
83
|
+
export function formatUntrackCommands(files) {
|
|
84
|
+
const unique = [...new Set(files)];
|
|
85
|
+
return unique.map(f => `git rm -r --cached "${f}"`);
|
|
86
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { formatUntrackCommands } from './tracked-files-detector.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render a plain-text warning when framework files are tracked by git.
|
|
5
|
+
*
|
|
6
|
+
* Non-blocking — the caller must decide whether to halt the upgrade.
|
|
7
|
+
* No ANSI color codes: output is suitable for piping and log files.
|
|
8
|
+
*
|
|
9
|
+
* @param {string[]} files - tracked framework paths detected by detectTrackedFrameworkFiles()
|
|
10
|
+
* @returns {string} warning text ready to print to stdout/stderr
|
|
11
|
+
*/
|
|
12
|
+
export function renderTrackedFilesWarning(files) {
|
|
13
|
+
if (!files || files.length === 0) return '';
|
|
14
|
+
|
|
15
|
+
const bullet = files.map(f => ` - ${f}`).join('\n');
|
|
16
|
+
const commands = formatUntrackCommands(files);
|
|
17
|
+
const cmdBlock = commands.join(' \\\n ');
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
'',
|
|
21
|
+
'================================================================',
|
|
22
|
+
'WARNING: Chati framework files are tracked by git',
|
|
23
|
+
'================================================================',
|
|
24
|
+
'',
|
|
25
|
+
'The following Chati-owned paths are currently committed to git:',
|
|
26
|
+
'',
|
|
27
|
+
bullet,
|
|
28
|
+
'',
|
|
29
|
+
'Why this matters:',
|
|
30
|
+
' - Framework internals (agents, hooks, session state) are not',
|
|
31
|
+
' project source code and should not be versioned alongside it.',
|
|
32
|
+
' - Committing them risks leaking internal IP and stale runtime',
|
|
33
|
+
' state (session locks, memory caches, team mailboxes) to',
|
|
34
|
+
' remote repositories or collaborators.',
|
|
35
|
+
' - Backup directories inside .chati.dev/ can be multi-megabyte',
|
|
36
|
+
' and will bloat your git history permanently.',
|
|
37
|
+
'',
|
|
38
|
+
'What to do:',
|
|
39
|
+
' Run the commands below to stop tracking these paths.',
|
|
40
|
+
' The files remain on disk — only git tracking is removed.',
|
|
41
|
+
'',
|
|
42
|
+
' ' + cmdBlock,
|
|
43
|
+
' git commit -m "chore: untrack Chati framework files from git"',
|
|
44
|
+
'',
|
|
45
|
+
'You can run these commands now or after the upgrade completes.',
|
|
46
|
+
'The upgrade will continue regardless.',
|
|
47
|
+
'================================================================',
|
|
48
|
+
'',
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Render a banner shown at the start of `chati upgrade`.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} fromVersion - current installed version (e.g. "4.2.3")
|
|
56
|
+
* @param {string} toVersion - target version (e.g. "4.3.0")
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function renderUpgradeBanner(fromVersion, toVersion) {
|
|
60
|
+
return [
|
|
61
|
+
'',
|
|
62
|
+
'================================================================',
|
|
63
|
+
`Chati.dev Upgrade: v${fromVersion} -> v${toVersion}`,
|
|
64
|
+
'================================================================',
|
|
65
|
+
'',
|
|
66
|
+
'Steps: backup -> migrations -> sync files -> validate -> done',
|
|
67
|
+
'',
|
|
68
|
+
].join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render a success message shown after a completed upgrade.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} fromVersion - version upgraded from
|
|
75
|
+
* @param {string} toVersion - version upgraded to
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
export function renderUpgradeSuccess(fromVersion, toVersion) {
|
|
79
|
+
return [
|
|
80
|
+
'',
|
|
81
|
+
'================================================================',
|
|
82
|
+
`Chati.dev successfully upgraded: v${fromVersion} -> v${toVersion}`,
|
|
83
|
+
'================================================================',
|
|
84
|
+
'',
|
|
85
|
+
'Next steps:',
|
|
86
|
+
' 1. Review any warnings printed above.',
|
|
87
|
+
' 2. If framework files were tracked by git, run the provided',
|
|
88
|
+
' git rm --cached commands and commit the result.',
|
|
89
|
+
' 3. Type /chati in your project to resume where you left off.',
|
|
90
|
+
'',
|
|
91
|
+
'Release notes: https://github.com/chati-dev/chati.dev/releases',
|
|
92
|
+
'',
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
+
import { resolveFrameworkDir } from './framework-dir.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Parse provider configuration from config.yaml.
|
|
@@ -16,7 +17,7 @@ import { join } from 'path';
|
|
|
16
17
|
* @returns {{ primary: string, enabled: string[], raw: string|null }}
|
|
17
18
|
*/
|
|
18
19
|
export function parseProviderConfig(projectDir) {
|
|
19
|
-
const configPath = join(projectDir,
|
|
20
|
+
const configPath = join(projectDir, resolveFrameworkDir(projectDir), 'config.yaml');
|
|
20
21
|
if (!existsSync(configPath)) {
|
|
21
22
|
return { primary: 'claude', enabled: ['claude'], raw: null };
|
|
22
23
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
|
+
import { resolveFrameworkDir } from './framework-dir.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* All known feature flags with their default values.
|
|
@@ -49,7 +50,7 @@ export function isFeatureEnabled(projectDir, featureName) {
|
|
|
49
50
|
* @returns {Record<string, boolean>} Map of feature name to enabled status
|
|
50
51
|
*/
|
|
51
52
|
export function getEnabledFeatures(projectDir) {
|
|
52
|
-
const configPath = join(projectDir,
|
|
53
|
+
const configPath = join(projectDir, resolveFrameworkDir(projectDir), 'config.yaml');
|
|
53
54
|
if (!existsSync(configPath)) {
|
|
54
55
|
return { ...DEFAULTS };
|
|
55
56
|
}
|