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.
Files changed (200) hide show
  1. package/README.md +3 -3
  2. package/framework/agents/build/dev.md +343 -0
  3. package/framework/agents/clarity/architect.md +112 -0
  4. package/framework/agents/clarity/brief.md +182 -0
  5. package/framework/agents/clarity/brownfield-wu.md +181 -0
  6. package/framework/agents/clarity/detail.md +110 -0
  7. package/framework/agents/clarity/greenfield-wu.md +153 -0
  8. package/framework/agents/clarity/ux.md +112 -0
  9. package/framework/config.yaml +3 -3
  10. package/framework/constitution.md +31 -1
  11. package/framework/context/governance.md +37 -0
  12. package/framework/context/protocols.md +34 -0
  13. package/framework/context/quality.md +27 -0
  14. package/framework/context/root.md +24 -0
  15. package/framework/domains/agents/architect.yaml +51 -0
  16. package/framework/domains/agents/brief.yaml +47 -0
  17. package/framework/domains/agents/brownfield-wu.yaml +49 -0
  18. package/framework/domains/agents/detail.yaml +47 -0
  19. package/framework/domains/agents/dev.yaml +49 -0
  20. package/framework/domains/agents/devops.yaml +43 -0
  21. package/framework/domains/agents/greenfield-wu.yaml +47 -0
  22. package/framework/domains/agents/orchestrator.yaml +49 -0
  23. package/framework/domains/agents/phases.yaml +47 -0
  24. package/framework/domains/agents/qa-implementation.yaml +43 -0
  25. package/framework/domains/agents/qa-planning.yaml +44 -0
  26. package/framework/domains/agents/tasks.yaml +48 -0
  27. package/framework/domains/agents/ux.yaml +50 -0
  28. package/framework/domains/constitution.yaml +77 -0
  29. package/framework/domains/global.yaml +64 -0
  30. package/framework/domains/workflows/brownfield-discovery.yaml +16 -0
  31. package/framework/domains/workflows/brownfield-fullstack.yaml +26 -0
  32. package/framework/domains/workflows/brownfield-service.yaml +22 -0
  33. package/framework/domains/workflows/brownfield-ui.yaml +22 -0
  34. package/framework/domains/workflows/greenfield-fullstack.yaml +26 -0
  35. package/framework/hooks/constitution-guard.js +101 -0
  36. package/framework/hooks/mode-governance.js +92 -0
  37. package/framework/hooks/model-governance.js +76 -0
  38. package/framework/hooks/prism-engine.js +89 -0
  39. package/framework/hooks/session-digest.js +60 -0
  40. package/framework/hooks/settings.json +44 -0
  41. package/framework/migrations/v1.4-to-v2.0.yaml +167 -0
  42. package/framework/migrations/v2.0-to-v2.0.1.yaml +132 -0
  43. package/framework/orchestrator/chati.md +284 -6
  44. package/framework/tasks/architect-api-design.md +63 -0
  45. package/framework/tasks/architect-consolidate.md +47 -0
  46. package/framework/tasks/architect-db-design.md +73 -0
  47. package/framework/tasks/architect-design.md +95 -0
  48. package/framework/tasks/architect-security-review.md +62 -0
  49. package/framework/tasks/architect-stack-selection.md +53 -0
  50. package/framework/tasks/brief-consolidate.md +249 -0
  51. package/framework/tasks/brief-constraint-identify.md +277 -0
  52. package/framework/tasks/brief-extract-requirements.md +339 -0
  53. package/framework/tasks/brief-stakeholder-map.md +176 -0
  54. package/framework/tasks/brief-validate-completeness.md +121 -0
  55. package/framework/tasks/brownfield-wu-architecture-map.md +394 -0
  56. package/framework/tasks/brownfield-wu-deep-discovery.md +312 -0
  57. package/framework/tasks/brownfield-wu-dependency-scan.md +359 -0
  58. package/framework/tasks/brownfield-wu-migration-plan.md +483 -0
  59. package/framework/tasks/brownfield-wu-report.md +325 -0
  60. package/framework/tasks/brownfield-wu-risk-assess.md +424 -0
  61. package/framework/tasks/detail-acceptance-criteria.md +372 -0
  62. package/framework/tasks/detail-consolidate.md +138 -0
  63. package/framework/tasks/detail-edge-case-analysis.md +300 -0
  64. package/framework/tasks/detail-expand-prd.md +389 -0
  65. package/framework/tasks/detail-nfr-extraction.md +223 -0
  66. package/framework/tasks/dev-code-review.md +404 -0
  67. package/framework/tasks/dev-consolidate.md +543 -0
  68. package/framework/tasks/dev-debug.md +322 -0
  69. package/framework/tasks/dev-implement.md +252 -0
  70. package/framework/tasks/dev-iterate.md +411 -0
  71. package/framework/tasks/dev-pr-prepare.md +497 -0
  72. package/framework/tasks/dev-refactor.md +342 -0
  73. package/framework/tasks/dev-test-write.md +306 -0
  74. package/framework/tasks/devops-ci-setup.md +412 -0
  75. package/framework/tasks/devops-consolidate.md +712 -0
  76. package/framework/tasks/devops-deploy-config.md +598 -0
  77. package/framework/tasks/devops-monitoring-setup.md +658 -0
  78. package/framework/tasks/devops-release-prepare.md +673 -0
  79. package/framework/tasks/greenfield-wu-analyze-empty.md +169 -0
  80. package/framework/tasks/greenfield-wu-report.md +266 -0
  81. package/framework/tasks/greenfield-wu-scaffold-detection.md +203 -0
  82. package/framework/tasks/greenfield-wu-tech-stack-assess.md +255 -0
  83. package/framework/tasks/orchestrator-deviation.md +260 -0
  84. package/framework/tasks/orchestrator-escalate.md +276 -0
  85. package/framework/tasks/orchestrator-handoff.md +243 -0
  86. package/framework/tasks/orchestrator-health.md +372 -0
  87. package/framework/tasks/orchestrator-mode-switch.md +262 -0
  88. package/framework/tasks/orchestrator-resume.md +189 -0
  89. package/framework/tasks/orchestrator-route.md +169 -0
  90. package/framework/tasks/orchestrator-spawn-terminal.md +358 -0
  91. package/framework/tasks/orchestrator-status.md +260 -0
  92. package/framework/tasks/orchestrator-suggest-mode.md +372 -0
  93. package/framework/tasks/phases-breakdown.md +91 -0
  94. package/framework/tasks/phases-dependency-mapping.md +67 -0
  95. package/framework/tasks/phases-mvp-scoping.md +94 -0
  96. package/framework/tasks/qa-impl-consolidate.md +522 -0
  97. package/framework/tasks/qa-impl-performance-test.md +487 -0
  98. package/framework/tasks/qa-impl-regression-check.md +413 -0
  99. package/framework/tasks/qa-impl-sast-scan.md +402 -0
  100. package/framework/tasks/qa-impl-test-execute.md +344 -0
  101. package/framework/tasks/qa-impl-verdict.md +339 -0
  102. package/framework/tasks/qa-planning-consolidate.md +309 -0
  103. package/framework/tasks/qa-planning-coverage-plan.md +338 -0
  104. package/framework/tasks/qa-planning-gate-define.md +339 -0
  105. package/framework/tasks/qa-planning-risk-matrix.md +631 -0
  106. package/framework/tasks/qa-planning-test-strategy.md +217 -0
  107. package/framework/tasks/tasks-acceptance-write.md +75 -0
  108. package/framework/tasks/tasks-consolidate.md +57 -0
  109. package/framework/tasks/tasks-decompose.md +80 -0
  110. package/framework/tasks/tasks-estimate.md +66 -0
  111. package/framework/tasks/ux-a11y-check.md +49 -0
  112. package/framework/tasks/ux-component-map.md +55 -0
  113. package/framework/tasks/ux-consolidate.md +46 -0
  114. package/framework/tasks/ux-user-flow.md +46 -0
  115. package/framework/tasks/ux-wireframe.md +76 -0
  116. package/package.json +1 -1
  117. package/scripts/bundle-framework.js +2 -0
  118. package/scripts/changelog-generator.js +222 -0
  119. package/scripts/codebase-mapper.js +728 -0
  120. package/scripts/commit-message-generator.js +167 -0
  121. package/scripts/coverage-analyzer.js +260 -0
  122. package/scripts/dependency-analyzer.js +280 -0
  123. package/scripts/framework-analyzer.js +308 -0
  124. package/scripts/generate-constitution-domain.js +253 -0
  125. package/scripts/health-check.js +481 -0
  126. package/scripts/ide-sync.js +327 -0
  127. package/scripts/performance-analyzer.js +325 -0
  128. package/scripts/plan-tracker.js +278 -0
  129. package/scripts/populate-entity-registry.js +481 -0
  130. package/scripts/pr-review.js +317 -0
  131. package/scripts/rollback-manager.js +310 -0
  132. package/scripts/stuck-detector.js +343 -0
  133. package/scripts/test-quality-assessment.js +257 -0
  134. package/scripts/validate-agents.js +367 -0
  135. package/scripts/validate-tasks.js +465 -0
  136. package/src/autonomy/autonomous-gate.js +293 -0
  137. package/src/autonomy/index.js +51 -0
  138. package/src/autonomy/mode-manager.js +225 -0
  139. package/src/autonomy/mode-suggester.js +283 -0
  140. package/src/autonomy/progress-reporter.js +268 -0
  141. package/src/autonomy/safety-net.js +320 -0
  142. package/src/context/bracket-tracker.js +79 -0
  143. package/src/context/domain-loader.js +107 -0
  144. package/src/context/engine.js +144 -0
  145. package/src/context/formatter.js +184 -0
  146. package/src/context/index.js +4 -0
  147. package/src/context/layers/l0-constitution.js +28 -0
  148. package/src/context/layers/l1-global.js +37 -0
  149. package/src/context/layers/l2-agent.js +39 -0
  150. package/src/context/layers/l3-workflow.js +42 -0
  151. package/src/context/layers/l4-task.js +24 -0
  152. package/src/decision/analyzer.js +167 -0
  153. package/src/decision/engine.js +270 -0
  154. package/src/decision/index.js +38 -0
  155. package/src/decision/registry-healer.js +450 -0
  156. package/src/decision/registry-updater.js +330 -0
  157. package/src/gates/circuit-breaker.js +119 -0
  158. package/src/gates/g1-planning-complete.js +153 -0
  159. package/src/gates/g2-qa-planning.js +153 -0
  160. package/src/gates/g3-implementation.js +188 -0
  161. package/src/gates/g4-qa-implementation.js +207 -0
  162. package/src/gates/g5-deploy-ready.js +180 -0
  163. package/src/gates/gate-base.js +144 -0
  164. package/src/gates/index.js +46 -0
  165. package/src/installer/brownfield-upgrader.js +249 -0
  166. package/src/installer/core.js +55 -3
  167. package/src/installer/file-hasher.js +51 -0
  168. package/src/installer/manifest.js +117 -0
  169. package/src/installer/templates.js +17 -15
  170. package/src/installer/transaction.js +229 -0
  171. package/src/installer/validator.js +18 -1
  172. package/src/memory/agent-memory.js +255 -0
  173. package/src/memory/gotchas-injector.js +72 -0
  174. package/src/memory/gotchas.js +361 -0
  175. package/src/memory/index.js +35 -0
  176. package/src/memory/search.js +233 -0
  177. package/src/memory/session-digest.js +239 -0
  178. package/src/merger/env-merger.js +112 -0
  179. package/src/merger/index.js +56 -0
  180. package/src/merger/replace-merger.js +51 -0
  181. package/src/merger/yaml-merger.js +127 -0
  182. package/src/orchestrator/agent-selector.js +285 -0
  183. package/src/orchestrator/deviation-handler.js +350 -0
  184. package/src/orchestrator/handoff-engine.js +271 -0
  185. package/src/orchestrator/index.js +67 -0
  186. package/src/orchestrator/intent-classifier.js +264 -0
  187. package/src/orchestrator/pipeline-manager.js +492 -0
  188. package/src/orchestrator/pipeline-state.js +223 -0
  189. package/src/orchestrator/session-manager.js +409 -0
  190. package/src/tasks/executor.js +195 -0
  191. package/src/tasks/handoff.js +226 -0
  192. package/src/tasks/index.js +4 -0
  193. package/src/tasks/loader.js +210 -0
  194. package/src/tasks/router.js +182 -0
  195. package/src/terminal/collector.js +216 -0
  196. package/src/terminal/index.js +30 -0
  197. package/src/terminal/isolation.js +129 -0
  198. package/src/terminal/monitor.js +277 -0
  199. package/src/terminal/spawner.js +269 -0
  200. package/src/upgrade/checker.js +1 -1
@@ -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
- 'i18n', 'migrations', 'data',
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. Create/update CLAUDE.md at root
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
- ## Quick Start
81
- Type \`/chati\` to activate the orchestrator. It will guide you through the entire process.
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
- ## Key Files
91
- - **Session**: \`.chati/session.yaml\` (runtime state)
92
- - **Constitution**: \`chati.dev/constitution.md\` (governance)
93
- - **Orchestrator**: \`chati.dev/orchestrator/chati.md\` (entry point)
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 agents (Protocol 5.4)_
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 >= 16;
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
  }