chati-dev 1.4.0 → 2.0.1
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 +3 -3
- package/framework/agents/build/dev.md +343 -0
- package/framework/agents/clarity/architect.md +112 -0
- package/framework/agents/clarity/brief.md +182 -0
- package/framework/agents/clarity/brownfield-wu.md +181 -0
- package/framework/agents/clarity/detail.md +110 -0
- package/framework/agents/clarity/greenfield-wu.md +153 -0
- package/framework/agents/clarity/ux.md +112 -0
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +31 -1
- package/framework/context/governance.md +37 -0
- package/framework/context/protocols.md +34 -0
- package/framework/context/quality.md +27 -0
- package/framework/context/root.md +24 -0
- package/framework/domains/agents/architect.yaml +51 -0
- package/framework/domains/agents/brief.yaml +47 -0
- package/framework/domains/agents/brownfield-wu.yaml +49 -0
- package/framework/domains/agents/detail.yaml +47 -0
- package/framework/domains/agents/dev.yaml +49 -0
- package/framework/domains/agents/devops.yaml +43 -0
- package/framework/domains/agents/greenfield-wu.yaml +47 -0
- package/framework/domains/agents/orchestrator.yaml +49 -0
- package/framework/domains/agents/phases.yaml +47 -0
- package/framework/domains/agents/qa-implementation.yaml +43 -0
- package/framework/domains/agents/qa-planning.yaml +44 -0
- package/framework/domains/agents/tasks.yaml +48 -0
- package/framework/domains/agents/ux.yaml +50 -0
- package/framework/domains/constitution.yaml +77 -0
- package/framework/domains/global.yaml +64 -0
- package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
- package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
- package/framework/domains/workflows/brownfield-service.yaml +22 -0
- package/framework/domains/workflows/brownfield-ui.yaml +22 -0
- package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
- package/framework/hooks/constitution-guard.js +101 -0
- package/framework/hooks/mode-governance.js +92 -0
- package/framework/hooks/model-governance.js +76 -0
- package/framework/hooks/prism-engine.js +89 -0
- package/framework/hooks/session-digest.js +60 -0
- package/framework/hooks/settings.json +44 -0
- package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
- package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
- package/framework/orchestrator/chati.md +284 -6
- package/framework/tasks/architect-api-design.md +63 -0
- package/framework/tasks/architect-consolidate.md +47 -0
- package/framework/tasks/architect-db-design.md +73 -0
- package/framework/tasks/architect-design.md +95 -0
- package/framework/tasks/architect-security-review.md +62 -0
- package/framework/tasks/architect-stack-selection.md +53 -0
- package/framework/tasks/brief-consolidate.md +249 -0
- package/framework/tasks/brief-constraint-identify.md +277 -0
- package/framework/tasks/brief-extract-requirements.md +339 -0
- package/framework/tasks/brief-stakeholder-map.md +176 -0
- package/framework/tasks/brief-validate-completeness.md +121 -0
- package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
- package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
- package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
- package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
- package/framework/tasks/brownfield-wu-report.md +325 -0
- package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
- package/framework/tasks/detail-acceptance-criteria.md +372 -0
- package/framework/tasks/detail-consolidate.md +138 -0
- package/framework/tasks/detail-edge-case-analysis.md +300 -0
- package/framework/tasks/detail-expand-prd.md +389 -0
- package/framework/tasks/detail-nfr-extraction.md +223 -0
- package/framework/tasks/dev-code-review.md +404 -0
- package/framework/tasks/dev-consolidate.md +543 -0
- package/framework/tasks/dev-debug.md +322 -0
- package/framework/tasks/dev-implement.md +252 -0
- package/framework/tasks/dev-iterate.md +411 -0
- package/framework/tasks/dev-pr-prepare.md +497 -0
- package/framework/tasks/dev-refactor.md +342 -0
- package/framework/tasks/dev-test-write.md +306 -0
- package/framework/tasks/devops-ci-setup.md +412 -0
- package/framework/tasks/devops-consolidate.md +712 -0
- package/framework/tasks/devops-deploy-config.md +598 -0
- package/framework/tasks/devops-monitoring-setup.md +658 -0
- package/framework/tasks/devops-release-prepare.md +673 -0
- package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
- package/framework/tasks/greenfield-wu-report.md +266 -0
- package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
- package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
- package/framework/tasks/orchestrator-deviation.md +260 -0
- package/framework/tasks/orchestrator-escalate.md +276 -0
- package/framework/tasks/orchestrator-handoff.md +243 -0
- package/framework/tasks/orchestrator-health.md +372 -0
- package/framework/tasks/orchestrator-mode-switch.md +262 -0
- package/framework/tasks/orchestrator-resume.md +189 -0
- package/framework/tasks/orchestrator-route.md +169 -0
- package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
- package/framework/tasks/orchestrator-status.md +260 -0
- package/framework/tasks/orchestrator-suggest-mode.md +372 -0
- package/framework/tasks/phases-breakdown.md +91 -0
- package/framework/tasks/phases-dependency-mapping.md +67 -0
- package/framework/tasks/phases-mvp-scoping.md +94 -0
- package/framework/tasks/qa-impl-consolidate.md +522 -0
- package/framework/tasks/qa-impl-performance-test.md +487 -0
- package/framework/tasks/qa-impl-regression-check.md +413 -0
- package/framework/tasks/qa-impl-sast-scan.md +402 -0
- package/framework/tasks/qa-impl-test-execute.md +344 -0
- package/framework/tasks/qa-impl-verdict.md +339 -0
- package/framework/tasks/qa-planning-consolidate.md +309 -0
- package/framework/tasks/qa-planning-coverage-plan.md +338 -0
- package/framework/tasks/qa-planning-gate-define.md +339 -0
- package/framework/tasks/qa-planning-risk-matrix.md +631 -0
- package/framework/tasks/qa-planning-test-strategy.md +217 -0
- package/framework/tasks/tasks-acceptance-write.md +75 -0
- package/framework/tasks/tasks-consolidate.md +57 -0
- package/framework/tasks/tasks-decompose.md +80 -0
- package/framework/tasks/tasks-estimate.md +66 -0
- package/framework/tasks/ux-a11y-check.md +49 -0
- package/framework/tasks/ux-component-map.md +55 -0
- package/framework/tasks/ux-consolidate.md +46 -0
- package/framework/tasks/ux-user-flow.md +46 -0
- package/framework/tasks/ux-wireframe.md +76 -0
- package/package.json +1 -1
- package/scripts/bundle-framework.js +2 -0
- package/scripts/changelog-generator.js +222 -0
- package/scripts/codebase-mapper.js +728 -0
- package/scripts/commit-message-generator.js +167 -0
- package/scripts/coverage-analyzer.js +260 -0
- package/scripts/dependency-analyzer.js +280 -0
- package/scripts/framework-analyzer.js +308 -0
- package/scripts/generate-constitution-domain.js +253 -0
- package/scripts/health-check.js +481 -0
- package/scripts/ide-sync.js +327 -0
- package/scripts/performance-analyzer.js +325 -0
- package/scripts/plan-tracker.js +278 -0
- package/scripts/populate-entity-registry.js +481 -0
- package/scripts/pr-review.js +317 -0
- package/scripts/rollback-manager.js +310 -0
- package/scripts/stuck-detector.js +343 -0
- package/scripts/test-quality-assessment.js +257 -0
- package/scripts/validate-agents.js +367 -0
- package/scripts/validate-tasks.js +465 -0
- package/src/autonomy/autonomous-gate.js +293 -0
- package/src/autonomy/index.js +51 -0
- package/src/autonomy/mode-manager.js +225 -0
- package/src/autonomy/mode-suggester.js +283 -0
- package/src/autonomy/progress-reporter.js +268 -0
- package/src/autonomy/safety-net.js +320 -0
- package/src/context/bracket-tracker.js +79 -0
- package/src/context/domain-loader.js +107 -0
- package/src/context/engine.js +144 -0
- package/src/context/formatter.js +184 -0
- package/src/context/index.js +4 -0
- package/src/context/layers/l0-constitution.js +28 -0
- package/src/context/layers/l1-global.js +37 -0
- package/src/context/layers/l2-agent.js +39 -0
- package/src/context/layers/l3-workflow.js +42 -0
- package/src/context/layers/l4-task.js +24 -0
- package/src/decision/analyzer.js +167 -0
- package/src/decision/engine.js +270 -0
- package/src/decision/index.js +38 -0
- package/src/decision/registry-healer.js +450 -0
- package/src/decision/registry-updater.js +330 -0
- package/src/gates/circuit-breaker.js +119 -0
- package/src/gates/g1-planning-complete.js +153 -0
- package/src/gates/g2-qa-planning.js +153 -0
- package/src/gates/g3-implementation.js +188 -0
- package/src/gates/g4-qa-implementation.js +207 -0
- package/src/gates/g5-deploy-ready.js +180 -0
- package/src/gates/gate-base.js +144 -0
- package/src/gates/index.js +46 -0
- package/src/installer/brownfield-upgrader.js +249 -0
- package/src/installer/core.js +55 -3
- package/src/installer/file-hasher.js +51 -0
- package/src/installer/manifest.js +117 -0
- package/src/installer/templates.js +17 -15
- package/src/installer/transaction.js +229 -0
- package/src/installer/validator.js +18 -1
- package/src/memory/agent-memory.js +255 -0
- package/src/memory/gotchas-injector.js +72 -0
- package/src/memory/gotchas.js +361 -0
- package/src/memory/index.js +35 -0
- package/src/memory/search.js +233 -0
- package/src/memory/session-digest.js +239 -0
- package/src/merger/env-merger.js +112 -0
- package/src/merger/index.js +56 -0
- package/src/merger/replace-merger.js +51 -0
- package/src/merger/yaml-merger.js +127 -0
- package/src/orchestrator/agent-selector.js +285 -0
- package/src/orchestrator/deviation-handler.js +350 -0
- package/src/orchestrator/handoff-engine.js +271 -0
- package/src/orchestrator/index.js +67 -0
- package/src/orchestrator/intent-classifier.js +264 -0
- package/src/orchestrator/pipeline-manager.js +492 -0
- package/src/orchestrator/pipeline-state.js +223 -0
- package/src/orchestrator/session-manager.js +409 -0
- package/src/tasks/executor.js +195 -0
- package/src/tasks/handoff.js +226 -0
- package/src/tasks/index.js +4 -0
- package/src/tasks/loader.js +210 -0
- package/src/tasks/router.js +182 -0
- package/src/terminal/collector.js +216 -0
- package/src/terminal/index.js +30 -0
- package/src/terminal/isolation.js +129 -0
- package/src/terminal/monitor.js +277 -0
- package/src/terminal/spawner.js +269 -0
- package/src/upgrade/checker.js +1 -1
package/src/installer/core.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join, dirname } from 'path';
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { IDE_CONFIGS } from '../config/ide-configs.js';
|
|
5
5
|
import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
|
|
6
|
-
import { generateSessionYaml, generateConfigYaml, generateClaudeMd } from './templates.js';
|
|
6
|
+
import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd } from './templates.js';
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
@@ -37,7 +37,8 @@ export async function installFramework(config) {
|
|
|
37
37
|
'agents/clarity', 'agents/quality', 'agents/build', 'agents/deploy',
|
|
38
38
|
'templates', 'workflows', 'quality-gates',
|
|
39
39
|
'schemas', 'frameworks', 'intelligence', 'patterns',
|
|
40
|
-
'
|
|
40
|
+
'hooks', 'domains', 'domains/agents', 'domains/workflows',
|
|
41
|
+
'i18n', 'migrations', 'data', 'context',
|
|
41
42
|
'artifacts/0-WU', 'artifacts/1-Brief', 'artifacts/2-PRD',
|
|
42
43
|
'artifacts/3-Architecture', 'artifacts/4-UX', 'artifacts/5-Phases',
|
|
43
44
|
'artifacts/6-Tasks', 'artifacts/7-QA-Planning', 'artifacts/8-Validation',
|
|
@@ -84,12 +85,30 @@ export async function installFramework(config) {
|
|
|
84
85
|
await configureIDE(targetDir, ideKey, selectedMCPs);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
// 4.
|
|
88
|
+
// 4. Copy context files to .claude/rules/chati/ (auto-loaded by Claude Code)
|
|
89
|
+
const rulesDir = join(targetDir, '.claude', 'rules', 'chati');
|
|
90
|
+
createDir(rulesDir);
|
|
91
|
+
const contextFiles = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
|
|
92
|
+
for (const file of contextFiles) {
|
|
93
|
+
const src = join(FRAMEWORK_SOURCE, 'context', file);
|
|
94
|
+
if (existsSync(src)) {
|
|
95
|
+
copyFileSync(src, join(rulesDir, file));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 5. Create CLAUDE.md (minimal — framework rules auto-loaded from .claude/rules/chati/)
|
|
88
100
|
writeFileSync(
|
|
89
101
|
join(targetDir, 'CLAUDE.md'),
|
|
90
102
|
generateClaudeMd({ projectName, projectType, language }),
|
|
91
103
|
'utf-8'
|
|
92
104
|
);
|
|
105
|
+
|
|
106
|
+
// 6. Create CLAUDE.local.md (runtime state — session lock, current agent)
|
|
107
|
+
writeFileSync(
|
|
108
|
+
join(targetDir, 'CLAUDE.local.md'),
|
|
109
|
+
generateClaudeLocalMd(),
|
|
110
|
+
'utf-8'
|
|
111
|
+
);
|
|
93
112
|
}
|
|
94
113
|
|
|
95
114
|
/**
|
|
@@ -149,6 +168,34 @@ function copyFrameworkFiles(destDir) {
|
|
|
149
168
|
'intelligence/decision-engine.md',
|
|
150
169
|
// Patterns
|
|
151
170
|
'patterns/elicitation.md',
|
|
171
|
+
// Hooks
|
|
172
|
+
'hooks/prism-engine.js',
|
|
173
|
+
'hooks/mode-governance.js',
|
|
174
|
+
'hooks/constitution-guard.js',
|
|
175
|
+
'hooks/session-digest.js',
|
|
176
|
+
'hooks/model-governance.js',
|
|
177
|
+
'hooks/settings.json',
|
|
178
|
+
// Domains (PRISM Context Engine)
|
|
179
|
+
'domains/constitution.yaml',
|
|
180
|
+
'domains/global.yaml',
|
|
181
|
+
'domains/agents/orchestrator.yaml',
|
|
182
|
+
'domains/agents/greenfield-wu.yaml',
|
|
183
|
+
'domains/agents/brownfield-wu.yaml',
|
|
184
|
+
'domains/agents/brief.yaml',
|
|
185
|
+
'domains/agents/detail.yaml',
|
|
186
|
+
'domains/agents/architect.yaml',
|
|
187
|
+
'domains/agents/ux.yaml',
|
|
188
|
+
'domains/agents/phases.yaml',
|
|
189
|
+
'domains/agents/tasks.yaml',
|
|
190
|
+
'domains/agents/qa-planning.yaml',
|
|
191
|
+
'domains/agents/qa-implementation.yaml',
|
|
192
|
+
'domains/agents/dev.yaml',
|
|
193
|
+
'domains/agents/devops.yaml',
|
|
194
|
+
'domains/workflows/greenfield-fullstack.yaml',
|
|
195
|
+
'domains/workflows/brownfield-fullstack.yaml',
|
|
196
|
+
'domains/workflows/brownfield-discovery.yaml',
|
|
197
|
+
'domains/workflows/brownfield-service.yaml',
|
|
198
|
+
'domains/workflows/brownfield-ui.yaml',
|
|
152
199
|
// i18n
|
|
153
200
|
'i18n/en.yaml',
|
|
154
201
|
'i18n/pt.yaml',
|
|
@@ -158,6 +205,11 @@ function copyFrameworkFiles(destDir) {
|
|
|
158
205
|
'migrations/v1.0-to-v1.1.yaml',
|
|
159
206
|
// Data
|
|
160
207
|
'data/entity-registry.yaml',
|
|
208
|
+
// Context (@ import chain for CLAUDE.md)
|
|
209
|
+
'context/root.md',
|
|
210
|
+
'context/governance.md',
|
|
211
|
+
'context/protocols.md',
|
|
212
|
+
'context/quality.md',
|
|
161
213
|
];
|
|
162
214
|
|
|
163
215
|
for (const file of filesToCopy) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize line endings: CRLF -> LF
|
|
6
|
+
* Ensures consistent hashing across platforms.
|
|
7
|
+
*/
|
|
8
|
+
function normalizeLineEndings(content) {
|
|
9
|
+
return content.replace(/\r\n/g, '\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute SHA-256 hash of a string content.
|
|
14
|
+
* Normalizes line endings before hashing.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} content - The string content to hash
|
|
17
|
+
* @returns {string} Hex-encoded SHA-256 digest
|
|
18
|
+
*/
|
|
19
|
+
export function hashContent(content) {
|
|
20
|
+
const normalized = normalizeLineEndings(content);
|
|
21
|
+
return createHash('sha256').update(normalized, 'utf-8').digest('hex');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compute SHA-256 hash of a file.
|
|
26
|
+
* Reads the file as UTF-8, normalizes line endings, then hashes.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} filePath - Absolute path to the file
|
|
29
|
+
* @returns {string} Hex-encoded SHA-256 digest
|
|
30
|
+
*/
|
|
31
|
+
export function hashFile(filePath) {
|
|
32
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
33
|
+
return hashContent(content);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compare two files by their SHA-256 hashes.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} pathA - Absolute path to first file
|
|
40
|
+
* @param {string} pathB - Absolute path to second file
|
|
41
|
+
* @returns {{ match: boolean, hashA: string, hashB: string }}
|
|
42
|
+
*/
|
|
43
|
+
export function compareFiles(pathA, pathB) {
|
|
44
|
+
const hashA = hashFile(pathA);
|
|
45
|
+
const hashB = hashFile(pathB);
|
|
46
|
+
return {
|
|
47
|
+
match: hashA === hashB,
|
|
48
|
+
hashA,
|
|
49
|
+
hashB,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { hashFile } from './file-hasher.js';
|
|
4
|
+
|
|
5
|
+
const MANIFEST_FILENAME = 'manifest.json';
|
|
6
|
+
const MANIFEST_DIR = '.chati';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate a manifest for a set of files under a root directory.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} rootDir - Root directory to scan from
|
|
12
|
+
* @param {string[]} relativePaths - Array of relative file paths to include
|
|
13
|
+
* @param {string} [version='0.0.0'] - Version string for the manifest
|
|
14
|
+
* @returns {{ version: string, createdAt: string, files: Record<string, { hash: string, size: number, type: string }> }}
|
|
15
|
+
*/
|
|
16
|
+
export function generateManifest(rootDir, relativePaths, version = '0.0.0') {
|
|
17
|
+
const files = {};
|
|
18
|
+
|
|
19
|
+
for (const relPath of relativePaths) {
|
|
20
|
+
const absPath = join(rootDir, relPath);
|
|
21
|
+
if (!existsSync(absPath)) continue;
|
|
22
|
+
|
|
23
|
+
const stat = statSync(absPath);
|
|
24
|
+
if (!stat.isFile()) continue;
|
|
25
|
+
|
|
26
|
+
const hash = hashFile(absPath);
|
|
27
|
+
const ext = relPath.split('.').pop() || '';
|
|
28
|
+
files[relPath] = {
|
|
29
|
+
hash,
|
|
30
|
+
size: stat.size,
|
|
31
|
+
type: ext,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
version,
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
files,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Save a manifest to .chati/manifest.json inside the target directory.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} targetDir - Target project directory
|
|
46
|
+
* @param {object} manifest - The manifest object to save
|
|
47
|
+
*/
|
|
48
|
+
export function saveManifest(targetDir, manifest) {
|
|
49
|
+
const manifestDir = join(targetDir, MANIFEST_DIR);
|
|
50
|
+
if (!existsSync(manifestDir)) {
|
|
51
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const manifestPath = join(manifestDir, MANIFEST_FILENAME);
|
|
55
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load a manifest from .chati/manifest.json inside the target directory.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} targetDir - Target project directory
|
|
62
|
+
* @returns {object|null} The manifest object, or null if not found
|
|
63
|
+
*/
|
|
64
|
+
export function loadManifest(targetDir) {
|
|
65
|
+
const manifestPath = join(targetDir, MANIFEST_DIR, MANIFEST_FILENAME);
|
|
66
|
+
if (!existsSync(manifestPath)) return null;
|
|
67
|
+
|
|
68
|
+
const raw = readFileSync(manifestPath, 'utf-8');
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compare two manifests to find added, removed, modified, and unchanged files.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} oldManifest - Previous manifest
|
|
76
|
+
* @param {object} newManifest - New manifest
|
|
77
|
+
* @returns {{ added: string[], removed: string[], modified: string[], unchanged: string[] }}
|
|
78
|
+
*/
|
|
79
|
+
export function compareManifests(oldManifest, newManifest) {
|
|
80
|
+
const oldFiles = oldManifest.files || {};
|
|
81
|
+
const newFiles = newManifest.files || {};
|
|
82
|
+
|
|
83
|
+
const oldKeys = new Set(Object.keys(oldFiles));
|
|
84
|
+
const newKeys = new Set(Object.keys(newFiles));
|
|
85
|
+
|
|
86
|
+
const added = [];
|
|
87
|
+
const removed = [];
|
|
88
|
+
const modified = [];
|
|
89
|
+
const unchanged = [];
|
|
90
|
+
|
|
91
|
+
// Files in new but not in old => added
|
|
92
|
+
for (const key of newKeys) {
|
|
93
|
+
if (!oldKeys.has(key)) {
|
|
94
|
+
added.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Files in old but not in new => removed
|
|
99
|
+
for (const key of oldKeys) {
|
|
100
|
+
if (!newKeys.has(key)) {
|
|
101
|
+
removed.push(key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Files in both => compare hashes
|
|
106
|
+
for (const key of oldKeys) {
|
|
107
|
+
if (newKeys.has(key)) {
|
|
108
|
+
if (oldFiles[key].hash === newFiles[key].hash) {
|
|
109
|
+
unchanged.push(key);
|
|
110
|
+
} else {
|
|
111
|
+
modified.push(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { added, removed, modified, unchanged };
|
|
117
|
+
}
|
|
@@ -64,7 +64,7 @@ export function generateConfigYaml(config) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Generate CLAUDE.md content
|
|
67
|
+
* Generate CLAUDE.md content (minimal — framework rules auto-loaded from .claude/rules/chati/)
|
|
68
68
|
*/
|
|
69
69
|
export function generateClaudeMd(config) {
|
|
70
70
|
const { projectName, projectType, language } = config;
|
|
@@ -73,32 +73,34 @@ export function generateClaudeMd(config) {
|
|
|
73
73
|
|
|
74
74
|
## Project Context
|
|
75
75
|
- **Type**: ${projectType === 'greenfield' ? 'Greenfield (new project)' : 'Brownfield (existing project)'}
|
|
76
|
-
- **State**: CLARITY (planning phase)
|
|
77
76
|
- **Language**: ${language}
|
|
78
|
-
- **Current Agent**: None (ready to start)
|
|
79
77
|
|
|
80
|
-
##
|
|
81
|
-
|
|
78
|
+
## chati.dev
|
|
79
|
+
Framework rules loaded from \`.claude/rules/chati/\`. Runtime state in \`CLAUDE.local.md\`.
|
|
80
|
+
Type \`/chati\` to start.
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate CLAUDE.local.md content (runtime state — auto-gitignored, never committed)
|
|
86
|
+
*/
|
|
87
|
+
export function generateClaudeLocalMd() {
|
|
88
|
+
return `# chati.dev Runtime State
|
|
82
89
|
|
|
83
90
|
## Session Lock
|
|
84
91
|
**Status: INACTIVE** — Type \`/chati\` to activate.
|
|
85
92
|
|
|
86
|
-
When active, ALL messages are routed through the chati.dev orchestrator. The user stays inside the system until they explicitly exit with \`/chati exit\`.
|
|
87
|
-
|
|
88
93
|
<!-- SESSION-LOCK:INACTIVE -->
|
|
89
94
|
|
|
90
|
-
##
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
94
|
-
|
|
95
|
-
## Pipeline
|
|
96
|
-
CLARITY (planning) -> BUILD (implementation) -> VALIDATE -> DEPLOY
|
|
95
|
+
## Current State
|
|
96
|
+
- **Agent**: None (ready to start)
|
|
97
|
+
- **Pipeline**: Pre-start
|
|
98
|
+
- **Mode**: interactive
|
|
97
99
|
|
|
98
100
|
## Recent Decisions
|
|
99
101
|
_No decisions yet. Start with /chati._
|
|
100
102
|
|
|
101
103
|
---
|
|
102
|
-
_Auto-updated by chati.dev
|
|
104
|
+
_Auto-updated by chati.dev orchestrator_
|
|
103
105
|
`;
|
|
104
106
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
writeFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
copyFileSync,
|
|
8
|
+
} from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transaction states.
|
|
13
|
+
* @readonly
|
|
14
|
+
* @enum {string}
|
|
15
|
+
*/
|
|
16
|
+
const STATE = {
|
|
17
|
+
PENDING: 'pending',
|
|
18
|
+
ACTIVE: 'active',
|
|
19
|
+
COMMITTED: 'committed',
|
|
20
|
+
ROLLED_BACK: 'rolled_back',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Atomic installation transaction with rollback support.
|
|
25
|
+
* Creates a backup before making changes, and can revert
|
|
26
|
+
* all operations if something goes wrong.
|
|
27
|
+
*/
|
|
28
|
+
export class InstallTransaction {
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} targetDir - The target directory for the installation
|
|
31
|
+
*/
|
|
32
|
+
constructor(targetDir) {
|
|
33
|
+
this._targetDir = targetDir;
|
|
34
|
+
this._state = STATE.PENDING;
|
|
35
|
+
this._backupDir = null;
|
|
36
|
+
this._operations = [];
|
|
37
|
+
this._reverseOps = [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the current transaction state.
|
|
42
|
+
* @returns {'pending' | 'active' | 'committed' | 'rolled_back'}
|
|
43
|
+
*/
|
|
44
|
+
getState() {
|
|
45
|
+
return this._state;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Begin the transaction.
|
|
50
|
+
* Creates a backup directory and snapshots the current state.
|
|
51
|
+
*/
|
|
52
|
+
begin() {
|
|
53
|
+
if (this._state !== STATE.PENDING) {
|
|
54
|
+
throw new Error(`Cannot begin transaction in state: ${this._state}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const timestamp = Date.now();
|
|
58
|
+
this._backupDir = join(this._targetDir, '.chati', `backup-${timestamp}`);
|
|
59
|
+
mkdirSync(this._backupDir, { recursive: true });
|
|
60
|
+
|
|
61
|
+
this._state = STATE.ACTIVE;
|
|
62
|
+
this._operations = [];
|
|
63
|
+
this._reverseOps = [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute a list of operations within the transaction.
|
|
68
|
+
* Each operation is recorded with its reverse for potential rollback.
|
|
69
|
+
*
|
|
70
|
+
* @param {Array<{ type: 'write'|'delete'|'mkdir', path: string, content?: string }>} operations
|
|
71
|
+
*/
|
|
72
|
+
execute(operations) {
|
|
73
|
+
if (this._state !== STATE.ACTIVE) {
|
|
74
|
+
throw new Error(`Cannot execute operations in state: ${this._state}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const op of operations) {
|
|
78
|
+
const absPath = join(this._targetDir, op.path);
|
|
79
|
+
|
|
80
|
+
switch (op.type) {
|
|
81
|
+
case 'write': {
|
|
82
|
+
// Backup existing file if it exists
|
|
83
|
+
if (existsSync(absPath)) {
|
|
84
|
+
const backupPath = join(this._backupDir, op.path);
|
|
85
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
86
|
+
copyFileSync(absPath, backupPath);
|
|
87
|
+
this._reverseOps.unshift({
|
|
88
|
+
type: 'restore',
|
|
89
|
+
path: op.path,
|
|
90
|
+
backupPath,
|
|
91
|
+
absPath,
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
this._reverseOps.unshift({
|
|
95
|
+
type: 'delete',
|
|
96
|
+
absPath,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Execute write
|
|
101
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
102
|
+
writeFileSync(absPath, op.content || '', 'utf-8');
|
|
103
|
+
this._operations.push(op);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
case 'delete': {
|
|
108
|
+
// Backup existing file before deleting
|
|
109
|
+
if (existsSync(absPath)) {
|
|
110
|
+
const backupPath = join(this._backupDir, op.path);
|
|
111
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
112
|
+
copyFileSync(absPath, backupPath);
|
|
113
|
+
this._reverseOps.unshift({
|
|
114
|
+
type: 'restore',
|
|
115
|
+
path: op.path,
|
|
116
|
+
backupPath,
|
|
117
|
+
absPath,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
unlinkSync(absPath);
|
|
121
|
+
}
|
|
122
|
+
this._operations.push(op);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'mkdir': {
|
|
127
|
+
if (!existsSync(absPath)) {
|
|
128
|
+
mkdirSync(absPath, { recursive: true });
|
|
129
|
+
this._reverseOps.unshift({
|
|
130
|
+
type: 'rmdir',
|
|
131
|
+
absPath,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
this._operations.push(op);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Unknown operation type: ${op.type}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Commit the transaction.
|
|
146
|
+
* Marks the transaction as complete and cleans up the backup directory.
|
|
147
|
+
*
|
|
148
|
+
* @returns {{ success: true, operationsExecuted: number }}
|
|
149
|
+
*/
|
|
150
|
+
commit() {
|
|
151
|
+
if (this._state !== STATE.ACTIVE) {
|
|
152
|
+
throw new Error(`Cannot commit transaction in state: ${this._state}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Clean up backup
|
|
156
|
+
if (this._backupDir && existsSync(this._backupDir)) {
|
|
157
|
+
rmSync(this._backupDir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this._state = STATE.COMMITTED;
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
operationsExecuted: this._operations.length,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Rollback the transaction.
|
|
169
|
+
* Reverses all operations that were executed, restoring from backup.
|
|
170
|
+
*
|
|
171
|
+
* @returns {{ success: true, operationsRolledBack: number }}
|
|
172
|
+
*/
|
|
173
|
+
rollback() {
|
|
174
|
+
if (this._state !== STATE.ACTIVE) {
|
|
175
|
+
throw new Error(`Cannot rollback transaction in state: ${this._state}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let rolledBack = 0;
|
|
179
|
+
|
|
180
|
+
for (const reverseOp of this._reverseOps) {
|
|
181
|
+
try {
|
|
182
|
+
switch (reverseOp.type) {
|
|
183
|
+
case 'restore': {
|
|
184
|
+
// Restore file from backup
|
|
185
|
+
mkdirSync(dirname(reverseOp.absPath), { recursive: true });
|
|
186
|
+
copyFileSync(reverseOp.backupPath, reverseOp.absPath);
|
|
187
|
+
rolledBack++;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'delete': {
|
|
192
|
+
// Delete a file that was created during the transaction
|
|
193
|
+
if (existsSync(reverseOp.absPath)) {
|
|
194
|
+
unlinkSync(reverseOp.absPath);
|
|
195
|
+
rolledBack++;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case 'rmdir': {
|
|
201
|
+
// Remove directory that was created (only if empty)
|
|
202
|
+
if (existsSync(reverseOp.absPath)) {
|
|
203
|
+
try {
|
|
204
|
+
rmSync(reverseOp.absPath, { recursive: true, force: true });
|
|
205
|
+
rolledBack++;
|
|
206
|
+
} catch {
|
|
207
|
+
// Directory may not be empty due to other changes; skip
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// Best-effort rollback: continue even if individual reversals fail
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Clean up backup directory
|
|
219
|
+
if (this._backupDir && existsSync(this._backupDir)) {
|
|
220
|
+
rmSync(this._backupDir, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this._state = STATE.ROLLED_BACK;
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
operationsRolledBack: rolledBack,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -16,6 +16,7 @@ export async function validateInstallation(targetDir) {
|
|
|
16
16
|
intelligence: { pass: false, details: [] },
|
|
17
17
|
registry: { pass: false, details: [] },
|
|
18
18
|
memories: { pass: false, details: [] },
|
|
19
|
+
context: { pass: false, details: [] },
|
|
19
20
|
total: 0,
|
|
20
21
|
passed: 0,
|
|
21
22
|
};
|
|
@@ -59,7 +60,7 @@ export async function validateInstallation(targetDir) {
|
|
|
59
60
|
if (existsSync(constitutionPath)) {
|
|
60
61
|
const content = readFileSync(constitutionPath, 'utf-8');
|
|
61
62
|
const articleCount = (content.match(/^## Article/gm) || []).length;
|
|
62
|
-
results.constitution.pass = articleCount >=
|
|
63
|
+
results.constitution.pass = articleCount >= 17;
|
|
63
64
|
results.constitution.details.push({ articleCount });
|
|
64
65
|
}
|
|
65
66
|
results.total += 1;
|
|
@@ -149,5 +150,21 @@ export async function validateInstallation(targetDir) {
|
|
|
149
150
|
results.total += 1;
|
|
150
151
|
if (results.memories.pass) results.passed += 1;
|
|
151
152
|
|
|
153
|
+
// Check context files (source in chati.dev/context/, deployed to .claude/rules/chati/)
|
|
154
|
+
const contextFileNames = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
|
|
155
|
+
let contextCount = 0;
|
|
156
|
+
for (const file of contextFileNames) {
|
|
157
|
+
// Check source (chati.dev/context/) OR deployed (.claude/rules/chati/)
|
|
158
|
+
const inSource = existsSync(join(targetDir, 'chati.dev', 'context', file));
|
|
159
|
+
const inRules = existsSync(join(targetDir, '.claude', 'rules', 'chati', file));
|
|
160
|
+
if (inSource || inRules) {
|
|
161
|
+
contextCount++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
results.context.pass = contextCount === 4;
|
|
165
|
+
results.context.details.push({ found: contextCount, expected: 4 });
|
|
166
|
+
results.total += 1;
|
|
167
|
+
if (results.context.pass) results.passed += 1;
|
|
168
|
+
|
|
152
169
|
return results;
|
|
153
170
|
}
|