chati-dev 4.4.0 → 4.5.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 +29 -27
- package/bin/chati.js +251 -1
- package/framework/agents/plan/tasks.md +46 -1
- package/framework/config.yaml +3 -3
- package/framework/constitution.md +16 -17
- package/framework/context/governance.md +5 -5
- package/framework/context/root.md +5 -5
- package/framework/data/entity-registry.yaml +1 -1
- package/framework/manifest.json +1351 -0
- package/framework/manifest.sig +1 -0
- package/framework/orchestrator/chati.md +48 -14
- package/node_modules/@chati/browser-capability/README.md +10 -0
- package/node_modules/@chati/browser-capability/package.json +17 -0
- package/node_modules/@chati/browser-capability/src/index.js +165 -0
- package/node_modules/@chati/core/package.json +13 -0
- package/node_modules/@chati/core/src/index.js +111 -0
- package/node_modules/@chati/knowledge-context/package.json +17 -0
- package/node_modules/@chati/knowledge-context/src/index.js +202 -0
- package/node_modules/@chati/planning/package.json +17 -0
- package/node_modules/@chati/planning/src/index.js +367 -0
- package/node_modules/@chati/provider-registry/package.json +16 -0
- package/node_modules/@chati/provider-registry/src/index.js +132 -0
- package/node_modules/@chati/rail/README.md +24 -0
- package/node_modules/@chati/rail/package.json +19 -0
- package/node_modules/@chati/rail/src/index.js +437 -0
- package/node_modules/@chati/release-lane/README.md +24 -0
- package/node_modules/@chati/release-lane/package.json +17 -0
- package/node_modules/@chati/release-lane/src/index.js +172 -0
- package/node_modules/@chati/review-council/package.json +17 -0
- package/node_modules/@chati/review-council/src/index.js +264 -0
- package/node_modules/@chati/tracking-clickup/README.md +55 -0
- package/node_modules/@chati/tracking-clickup/package.json +17 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +293 -0
- package/package.json +25 -3
- package/src/config/claude-settings-generator.js +9 -6
- package/src/config/ide-configs.js +11 -0
- package/src/context/domain-loader.js +1 -1
- package/src/dashboard/data-reader.js +1 -1
- package/src/executors/runner.js +1 -1
- package/src/installer/core.js +33 -3
- package/src/installer/manifest.js +30 -4
- package/src/installer/scaffold-applier.js +1 -1
- package/src/installer/signing-public-key.pem +1 -1
- package/src/installer/templates.js +3 -3
- package/src/installer/validator.js +3 -2
- package/src/installer-v2/catalog-client.js +141 -0
- package/src/installer-v2/index.js +301 -0
- package/src/installer-v2/model-catalog-envelope.json +59 -0
- package/src/installer-v2/model-catalog.json +33 -0
- package/src/installer-v2/model-catalog.sig +1 -0
- package/src/installer-v2/wizard-installation.js +115 -0
- package/src/intelligence/registry-manager.js +3 -2
- package/src/license/client.js +1 -1
- package/src/memory/magic-docs.js +6 -1
- package/src/memory/session-digest.js +1 -1
- package/src/merger/yaml-merger.js +1 -1
- package/src/orchestrator/browser-runtime.js +25 -0
- package/src/orchestrator/cli.js +112 -15
- package/src/orchestrator/clickup-projection.js +84 -0
- package/src/orchestrator/clickup-runtime.js +13 -0
- package/src/orchestrator/deviation-handler.js +5 -3
- package/src/orchestrator/knowledge-runtime.js +64 -0
- package/src/orchestrator/planning-runtime.js +127 -0
- package/src/orchestrator/rail-runtime.js +421 -0
- package/src/orchestrator/release-runtime.js +14 -0
- package/src/orchestrator/review-runtime.js +74 -0
- package/src/orchestrator/runtime-installation-v2.js +38 -0
- package/src/orchestrator/session-manager.js +1 -1
- package/src/telemetry/config.js +1 -1
- package/src/terminal/adapters/grok-adapter.js +16 -0
- package/src/terminal/adapters/index.js +1 -0
- package/src/terminal/cli-registry.js +14 -0
- package/src/terminal/prompt-builder.js +2 -0
- package/src/terminal/run-agent.js +3 -0
- package/src/terminal/run-parallel.js +35 -24
- package/src/terminal/spawner.js +7 -1
- package/src/terminal/team-task-list.js +1 -1
- package/src/upgrade/backup.js +22 -44
- package/src/upgrade/checker.js +1 -1
- package/src/upgrade/migrator.js +52 -13
- package/src/utils/constitution-meta.js +12 -0
- package/src/utils/feature-flags.js +25 -1
- package/src/wizard/i18n.js +8 -2
- package/src/wizard/index.js +39 -4
- package/src/wizard/questions.js +59 -2
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import { buildAgentPrompt } from './prompt-builder.js';
|
|
19
|
-
import { spawnParallelGroup, spawnTerminal } from './spawner.js';
|
|
19
|
+
import { spawnParallelGroup, spawnTerminal, killTerminal } from './spawner.js';
|
|
20
20
|
import { TerminalMonitor } from './monitor.js';
|
|
21
21
|
import { collectResults, mergeHandoffs, buildConsolidatedHandoff } from './collector.js';
|
|
22
22
|
import { parseAgentOutput } from './handoff-parser.js';
|
|
@@ -45,6 +45,19 @@ function parseArgs(argv) {
|
|
|
45
45
|
return args;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function createSpawnConfig({ agent, taskId, promptResult, projectDir, timeout, strictProvider }) {
|
|
49
|
+
return {
|
|
50
|
+
agent,
|
|
51
|
+
taskId,
|
|
52
|
+
model: promptResult.model,
|
|
53
|
+
provider: promptResult.provider,
|
|
54
|
+
prompt: promptResult.prompt,
|
|
55
|
+
workingDir: projectDir,
|
|
56
|
+
timeout,
|
|
57
|
+
strictProvider,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
// ---------------------------------------------------------------------------
|
|
49
62
|
// Main
|
|
50
63
|
// ---------------------------------------------------------------------------
|
|
@@ -103,17 +116,14 @@ async function main() {
|
|
|
103
116
|
previousAgent,
|
|
104
117
|
sessionState,
|
|
105
118
|
provider: args.provider || null,
|
|
119
|
+
model: args.model || null,
|
|
120
|
+
strictProvider: args['strict-provider'] === 'true',
|
|
106
121
|
});
|
|
107
122
|
|
|
108
|
-
configs.push({
|
|
109
|
-
agent: agents[i],
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
provider: promptResult.provider,
|
|
113
|
-
prompt: promptResult.prompt,
|
|
114
|
-
workingDir: projectDir,
|
|
115
|
-
timeout,
|
|
116
|
-
});
|
|
123
|
+
configs.push(createSpawnConfig({
|
|
124
|
+
agent: agents[i], taskId: taskIds[i], promptResult, projectDir, timeout,
|
|
125
|
+
strictProvider: args['strict-provider'] === 'true',
|
|
126
|
+
}));
|
|
117
127
|
} catch (err) {
|
|
118
128
|
outputError(`Failed to build prompt for ${agents[i]}: ${err.message}`);
|
|
119
129
|
process.exit(1);
|
|
@@ -254,13 +264,13 @@ async function main() {
|
|
|
254
264
|
* @param {number} timeout - Per-agent timeout in ms
|
|
255
265
|
* @returns {Promise<{ groupId: string, terminals: object[] }>}
|
|
256
266
|
*/
|
|
257
|
-
async function sequentialFallback(configs, timeout) {
|
|
267
|
+
async function sequentialFallback(configs, timeout, _spawnFn = spawnTerminal) {
|
|
258
268
|
const groupId = `seq-fallback-${Date.now()}`;
|
|
259
269
|
const terminals = [];
|
|
260
270
|
|
|
261
271
|
for (let i = 0; i < configs.length; i++) {
|
|
262
272
|
const cfg = configs[i];
|
|
263
|
-
const
|
|
273
|
+
const handle = _spawnFn({
|
|
264
274
|
agent: cfg.agent,
|
|
265
275
|
taskId: cfg.taskId,
|
|
266
276
|
model: cfg.model,
|
|
@@ -268,28 +278,29 @@ async function sequentialFallback(configs, timeout) {
|
|
|
268
278
|
prompt: cfg.prompt,
|
|
269
279
|
workingDir: cfg.workingDir,
|
|
270
280
|
timeout: cfg.timeout || timeout,
|
|
281
|
+
strictProvider: cfg.strictProvider,
|
|
271
282
|
});
|
|
272
283
|
|
|
273
|
-
// Wait for
|
|
284
|
+
// Wait for the REAL process exit before spawning the next agent. The old
|
|
285
|
+
// implementation probed terminal.onExit?.(), a method TerminalHandle never
|
|
286
|
+
// had, so every agent "completed" instantly and the fallback ran the whole
|
|
287
|
+
// group in parallel - defeating its purpose (shedding rate-limit pressure).
|
|
274
288
|
await new Promise((resolve) => {
|
|
275
|
-
|
|
276
|
-
terminal.kill?.();
|
|
289
|
+
if (!handle?.process || handle.status !== 'running') {
|
|
277
290
|
resolve();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const timer = setTimeout(() => {
|
|
294
|
+
killTerminal(handle).catch(() => {}).then(resolve, resolve);
|
|
278
295
|
}, (cfg.timeout || timeout) + 5_000);
|
|
279
296
|
|
|
280
|
-
|
|
297
|
+
handle.process.once('exit', () => {
|
|
281
298
|
clearTimeout(timer);
|
|
282
299
|
resolve();
|
|
283
300
|
});
|
|
284
|
-
|
|
285
|
-
// If terminal doesn't have onExit, resolve after a short poll
|
|
286
|
-
if (!terminal.onExit) {
|
|
287
|
-
clearTimeout(timer);
|
|
288
|
-
resolve();
|
|
289
|
-
}
|
|
290
301
|
});
|
|
291
302
|
|
|
292
|
-
terminals.push(
|
|
303
|
+
terminals.push(handle);
|
|
293
304
|
|
|
294
305
|
// Small delay between spawns to avoid rate limit pressure
|
|
295
306
|
if (i < configs.length - 1) {
|
|
@@ -328,4 +339,4 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
328
339
|
});
|
|
329
340
|
}
|
|
330
341
|
|
|
331
|
-
export { parseArgs, determineNextAgent, sequentialFallback };
|
|
342
|
+
export { parseArgs, createSpawnConfig, determineNextAgent, sequentialFallback };
|
package/src/terminal/spawner.js
CHANGED
|
@@ -107,7 +107,8 @@ export function cleanParentEnv(env) {
|
|
|
107
107
|
* @property {string} agent - Agent name (e.g. "architect")
|
|
108
108
|
* @property {string} taskId - Task identifier
|
|
109
109
|
* @property {string} [model] - LLM model tier name (e.g. opus, pro, codex, claude-sonnet)
|
|
110
|
-
* @property {string} [provider] - CLI provider name (claude, gemini, codex)
|
|
110
|
+
* @property {string} [provider] - CLI provider name (claude, gemini, codex, grok)
|
|
111
|
+
* @property {boolean} [strictProvider] - Reject a missing provider rather than using the legacy fallback
|
|
111
112
|
* @property {string} [prompt] - Full prompt string (from prompt-builder, piped via stdin)
|
|
112
113
|
* @property {object} [contextPayload] - Context to inject via env var
|
|
113
114
|
* @property {string[]} [writeScope] - Override write scope
|
|
@@ -181,6 +182,11 @@ export function buildSpawnCommand(config) {
|
|
|
181
182
|
args = adapterResult.args;
|
|
182
183
|
prompt = adapterResult.stdinPrompt;
|
|
183
184
|
} catch (err) {
|
|
185
|
+
if (config.strictProvider) {
|
|
186
|
+
const failure = new Error(`Selected provider "${providerName}" is unavailable: ${err.message}`);
|
|
187
|
+
failure.code = 'SELECTED_PROVIDER_UNAVAILABLE';
|
|
188
|
+
throw failure;
|
|
189
|
+
}
|
|
184
190
|
// Fallback to claude if provider resolution fails (backwards compatibility)
|
|
185
191
|
providerFallback = {
|
|
186
192
|
requested: providerName,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, realpathSync } from 'fs';
|
|
12
12
|
import { join, dirname, resolve, relative } from 'path';
|
|
13
|
-
import yaml from 'js-yaml';
|
|
13
|
+
import * as yaml from 'js-yaml';
|
|
14
14
|
import { withLock } from '../utils/file-lock.js';
|
|
15
15
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
package/src/upgrade/backup.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync, rmSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
4
|
+
import { FRAMEWORK_DIRS_TO_COPY } from '../installer/core.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The backup must cover exactly what the upgrade overwrites, or a rollback
|
|
8
|
+
* leaves the install in a chimera state (some dirs at the new version, some
|
|
9
|
+
* restored). The upgrade replaces every dir in FRAMEWORK_DIRS_TO_COPY (via
|
|
10
|
+
* copyFrameworkFiles), so that same list is the backup/restore scope - one
|
|
11
|
+
* source of truth, no asymmetry. Previously the backup saved 12 dirs and the
|
|
12
|
+
* restore returned only 10 (intelligence and data were backed up but never
|
|
13
|
+
* restored; hooks, domains, tasks, scaffold and others were never backed up
|
|
14
|
+
* at all).
|
|
15
|
+
*/
|
|
16
|
+
const BACKUP_DIRS = FRAMEWORK_DIRS_TO_COPY;
|
|
17
|
+
|
|
18
|
+
const BACKUP_FILES = [
|
|
19
|
+
'constitution.md',
|
|
20
|
+
'config.yaml',
|
|
21
|
+
];
|
|
4
22
|
|
|
5
23
|
/**
|
|
6
24
|
* Create backup of chati.dev/ directory before upgrade
|
|
@@ -17,28 +35,7 @@ export function createBackup(targetDir, currentVersion) {
|
|
|
17
35
|
|
|
18
36
|
mkdirSync(backupDir, { recursive: true });
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
const dirsToBackup = [
|
|
22
|
-
'orchestrator',
|
|
23
|
-
'agents',
|
|
24
|
-
'templates',
|
|
25
|
-
'workflows',
|
|
26
|
-
'quality-gates',
|
|
27
|
-
'schemas',
|
|
28
|
-
'frameworks',
|
|
29
|
-
'i18n',
|
|
30
|
-
'patterns',
|
|
31
|
-
'migrations',
|
|
32
|
-
'intelligence',
|
|
33
|
-
'data',
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const filesToBackup = [
|
|
37
|
-
'constitution.md',
|
|
38
|
-
'config.yaml',
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
for (const dir of dirsToBackup) {
|
|
38
|
+
for (const dir of BACKUP_DIRS) {
|
|
42
39
|
const src = join(frameworkDir, dir);
|
|
43
40
|
const dest = join(backupDir, dir);
|
|
44
41
|
if (existsSync(src)) {
|
|
@@ -46,7 +43,7 @@ export function createBackup(targetDir, currentVersion) {
|
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
for (const file of
|
|
46
|
+
for (const file of BACKUP_FILES) {
|
|
50
47
|
const src = join(frameworkDir, file);
|
|
51
48
|
const dest = join(backupDir, file);
|
|
52
49
|
if (existsSync(src)) {
|
|
@@ -68,26 +65,7 @@ export function restoreFromBackup(targetDir, version) {
|
|
|
68
65
|
throw new Error(`Backup not found: ${backupDir}`);
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
const dirsToRestore = [
|
|
73
|
-
'orchestrator',
|
|
74
|
-
'agents',
|
|
75
|
-
'templates',
|
|
76
|
-
'workflows',
|
|
77
|
-
'quality-gates',
|
|
78
|
-
'schemas',
|
|
79
|
-
'frameworks',
|
|
80
|
-
'i18n',
|
|
81
|
-
'patterns',
|
|
82
|
-
'migrations',
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const filesToRestore = [
|
|
86
|
-
'constitution.md',
|
|
87
|
-
'config.yaml',
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
for (const dir of dirsToRestore) {
|
|
68
|
+
for (const dir of BACKUP_DIRS) {
|
|
91
69
|
const src = join(backupDir, dir);
|
|
92
70
|
const dest = join(frameworkDir, dir);
|
|
93
71
|
if (existsSync(src)) {
|
|
@@ -98,7 +76,7 @@ export function restoreFromBackup(targetDir, version) {
|
|
|
98
76
|
}
|
|
99
77
|
}
|
|
100
78
|
|
|
101
|
-
for (const file of
|
|
79
|
+
for (const file of BACKUP_FILES) {
|
|
102
80
|
const src = join(backupDir, file);
|
|
103
81
|
const dest = join(frameworkDir, file);
|
|
104
82
|
if (existsSync(src)) {
|
package/src/upgrade/checker.js
CHANGED
package/src/upgrade/migrator.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync
|
|
|
2
2
|
import { dirname, join } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
5
|
-
import yaml from 'js-yaml';
|
|
5
|
+
import * as yaml from 'js-yaml';
|
|
6
6
|
import semver from 'semver';
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -42,13 +42,25 @@ export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
|
42
42
|
for (const file of files) {
|
|
43
43
|
try {
|
|
44
44
|
const content = yaml.load(readFileSync(join(migrationsDir, file), 'utf-8'));
|
|
45
|
-
|
|
45
|
+
// Two header schemas exist: the nested `migration: { from, to }` form and
|
|
46
|
+
// the older flat `from_version`/`to_version` form (with `operations`
|
|
47
|
+
// instead of `steps`). The flat files were silently skipped here, which
|
|
48
|
+
// hid them from every upgrade.
|
|
49
|
+
const migration = content?.migration
|
|
50
|
+
?? (content?.from_version && content?.to_version
|
|
51
|
+
? { from: content.from_version, to: content.to_version, description: content.description, breaking: content.breaking }
|
|
52
|
+
: null);
|
|
46
53
|
|
|
47
54
|
if (!migration?.from || !migration?.to) continue;
|
|
48
55
|
|
|
49
|
-
//
|
|
56
|
+
// A migration applies when its TARGET version lies inside the upgrade
|
|
57
|
+
// interval (fromVersion, toVersion]. The old condition compared the
|
|
58
|
+
// migration's `from` against the user's version with gte, which skipped
|
|
59
|
+
// every pending migration for users sitting ABOVE a migration's starting
|
|
60
|
+
// point (a 4.2.3 user never received v4.2-to-v4.3) and is wrong for
|
|
61
|
+
// users below it too (a 3.x user needs every step on the way up).
|
|
50
62
|
if (
|
|
51
|
-
semver.
|
|
63
|
+
semver.gt(migration.to, fromVersion) &&
|
|
52
64
|
semver.lte(migration.to, toVersion)
|
|
53
65
|
) {
|
|
54
66
|
applicable.push({
|
|
@@ -57,7 +69,7 @@ export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
|
57
69
|
to: migration.to,
|
|
58
70
|
description: migration.description || '',
|
|
59
71
|
breaking: migration.breaking || false,
|
|
60
|
-
steps: content.steps || [],
|
|
72
|
+
steps: content.steps || content.operations || [],
|
|
61
73
|
rollback: content.rollback || [],
|
|
62
74
|
});
|
|
63
75
|
}
|
|
@@ -73,14 +85,20 @@ export function findMigrations(targetDir, fromVersion, toVersion) {
|
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
/**
|
|
76
|
-
* Execute a single migration step
|
|
88
|
+
* Execute a single migration step. Exported for testing.
|
|
77
89
|
*/
|
|
78
|
-
async function executeMigrationStep(targetDir, step) {
|
|
90
|
+
export async function executeMigrationStep(targetDir, step, sourceFile = '') {
|
|
79
91
|
switch (step.type) {
|
|
80
92
|
case 'create_directory': {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
// Both shapes exist in the migration corpus: `path` (string) in the
|
|
94
|
+
// nested-schema files and `paths` (array) in the older flat-schema ones.
|
|
95
|
+
const dirs = Array.isArray(step.paths) ? step.paths : [step.path];
|
|
96
|
+
for (const rel of dirs) {
|
|
97
|
+
if (!rel) continue;
|
|
98
|
+
const dir = join(targetDir, rel);
|
|
99
|
+
if (!existsSync(dir) || !step.idempotent) {
|
|
100
|
+
mkdirSync(dir, { recursive: true });
|
|
101
|
+
}
|
|
84
102
|
}
|
|
85
103
|
break;
|
|
86
104
|
}
|
|
@@ -100,7 +118,19 @@ async function executeMigrationStep(targetDir, step) {
|
|
|
100
118
|
|
|
101
119
|
const content = yaml.load(readFileSync(filePath, 'utf-8')) || {};
|
|
102
120
|
|
|
103
|
-
if (step.
|
|
121
|
+
if (step.changes && typeof step.changes === 'object') {
|
|
122
|
+
// Older flat-schema shape: a `changes:` map of (possibly dotted)
|
|
123
|
+
// field -> value, applied as a plain set.
|
|
124
|
+
for (const [field, value] of Object.entries(step.changes)) {
|
|
125
|
+
const keys = field.split('.');
|
|
126
|
+
let obj = content;
|
|
127
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
128
|
+
if (!obj[keys[i]]) obj[keys[i]] = {};
|
|
129
|
+
obj = obj[keys[i]];
|
|
130
|
+
}
|
|
131
|
+
obj[keys[keys.length - 1]] = value;
|
|
132
|
+
}
|
|
133
|
+
} else if (step.operation === 'add_field') {
|
|
104
134
|
const keys = step.field.split('.');
|
|
105
135
|
let obj = content;
|
|
106
136
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
@@ -176,7 +206,16 @@ async function executeMigrationStep(targetDir, step) {
|
|
|
176
206
|
}
|
|
177
207
|
|
|
178
208
|
default:
|
|
179
|
-
//
|
|
209
|
+
// Copy-style steps (copy_file, copy_directory) from the older flat-schema
|
|
210
|
+
// migrations are subsumed by the upgrade's blanket copyFrameworkFiles, so
|
|
211
|
+
// skipping them is correct. Anything else unknown is surfaced instead of
|
|
212
|
+
// silently dropped, so an upgrader from a very old version knows what to
|
|
213
|
+
// review manually.
|
|
214
|
+
if (step.type !== 'copy_file' && step.type !== 'copy_directory') {
|
|
215
|
+
process.stderr.write(
|
|
216
|
+
`[chati] warning: migration step type "${step.type}"${sourceFile ? ` in ${sourceFile}` : ''} is not auto-executable; review the migration file manually\n`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
180
219
|
break;
|
|
181
220
|
}
|
|
182
221
|
}
|
|
@@ -196,7 +235,7 @@ export async function runMigrations(targetDir, fromVersion, toVersion) {
|
|
|
196
235
|
for (const migration of migrations) {
|
|
197
236
|
try {
|
|
198
237
|
for (const step of migration.steps) {
|
|
199
|
-
await executeMigrationStep(targetDir, step);
|
|
238
|
+
await executeMigrationStep(targetDir, step, migration.file);
|
|
200
239
|
}
|
|
201
240
|
results.push({ file: migration.file, success: true });
|
|
202
241
|
} catch (err) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Single source of truth for constitution metadata used by
|
|
3
|
+
* integrity checks.
|
|
4
|
+
*
|
|
5
|
+
* Every check that counts `## Article` headings (doctor, registry health,
|
|
6
|
+
* installer validator, stale-scan check 13) compares against THIS constant,
|
|
7
|
+
* so adding a constitutional article is a one-line bump here. The stale-scan
|
|
8
|
+
* asserts the canonical constitution.md actually contains this many articles,
|
|
9
|
+
* which keeps the constant honest.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const EXPECTED_ARTICLE_COUNT = 25;
|
|
@@ -14,9 +14,17 @@ import { resolveFrameworkDir } from './framework-dir.js';
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* All known feature flags with their default values.
|
|
17
|
-
* New features start as false (opt-in).
|
|
17
|
+
* New features start as false (opt-in). Defaults describe the state when
|
|
18
|
+
* config.yaml is absent; installed projects get their real values written by
|
|
19
|
+
* generateConfigYaml, and parseFeaturesSection only honors keys listed here,
|
|
20
|
+
* so EVERY boolean flag documented in config.yaml must appear in this map or
|
|
21
|
+
* the user's toggle is silently ignored (which froze 11 flags until 4.4.1).
|
|
22
|
+
* The numeric team_* and build_wave_size keys are intentionally absent: the
|
|
23
|
+
* parser is boolean-only and those values are read directly from config.yaml
|
|
24
|
+
* by their consumers.
|
|
18
25
|
*/
|
|
19
26
|
const DEFAULTS = {
|
|
27
|
+
// Core framework flags
|
|
20
28
|
hybrid_budget: false,
|
|
21
29
|
anti_dash: false,
|
|
22
30
|
rate_limiter_integration: false,
|
|
@@ -28,6 +36,22 @@ const DEFAULTS = {
|
|
|
28
36
|
tech_presets: false,
|
|
29
37
|
doctor_autofix: false,
|
|
30
38
|
brandbook: false,
|
|
39
|
+
// Intelligence upgrade flags
|
|
40
|
+
undercover_mode: false,
|
|
41
|
+
memory_extraction: false,
|
|
42
|
+
memory_consolidation: false,
|
|
43
|
+
daily_digest: false,
|
|
44
|
+
structured_session_memory: false,
|
|
45
|
+
static_prism_boundary: false,
|
|
46
|
+
token_bracket_estimation: false,
|
|
47
|
+
model_fallback: false,
|
|
48
|
+
frustration_detection: false,
|
|
49
|
+
bash_security_checks: false,
|
|
50
|
+
// Agent Teams (Article XXI). NOTE: isAgentTeamsEnabled in cli.js keeps its
|
|
51
|
+
// own reader with Claude-provider defaulting; this entry exists so a config
|
|
52
|
+
// toggle is at least visible through the generic API.
|
|
53
|
+
agent_teams: false,
|
|
54
|
+
// Execution Kinds (Article XXV)
|
|
31
55
|
execution_kinds: false,
|
|
32
56
|
};
|
|
33
57
|
|
package/src/wizard/i18n.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import yaml from 'js-yaml';
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
5
5
|
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -31,12 +31,18 @@ const FALLBACK_EN = {
|
|
|
31
31
|
provider_selection_title: 'Which AI providers will you use?',
|
|
32
32
|
editor_selection_title: 'Which editor IDEs should get rules files? (optional)',
|
|
33
33
|
primary_provider_title: 'Which is your primary CLI provider?',
|
|
34
|
+
execution_profile_title: 'Select the execution profile',
|
|
35
|
+
execution_profile_standard: 'Standard',
|
|
36
|
+
execution_profile_standard_hint: 'Provider-neutral local project workflow',
|
|
37
|
+
execution_profile_internal: 'Focus AI internal',
|
|
38
|
+
execution_profile_internal_hint: 'Requires ClickUp tracking and completion metrics',
|
|
39
|
+
execution_profile_label: 'Execution profile',
|
|
34
40
|
primary_provider_label: 'Primary Provider',
|
|
35
41
|
providers_label: 'Providers',
|
|
36
42
|
quick_start_switch_hint: 'Switch CLIs anytime — your session continues from where you left off',
|
|
37
43
|
created_overlays: 'Created provider overlay directories',
|
|
38
44
|
will_install: 'Will install:',
|
|
39
|
-
agents_count: 'Specialized
|
|
45
|
+
agents_count: 'Specialized discovery and planning agents with RAIL execution and release lanes',
|
|
40
46
|
workflows_count: '6 workflow blueprints',
|
|
41
47
|
templates_count: '6 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate, Quick Brief)',
|
|
42
48
|
constitution: 'Constitution (25 Articles + Preamble)',
|
package/src/wizard/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
3
|
import { join, dirname, basename } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { logBanner } from '../utils/logger.js';
|
|
6
|
-
import { stepLanguage, stepProjectType, stepProviderSelection, stepEditorSelection, stepPrimaryProvider, stepConfirmation, stepTermsOfUse } from './questions.js';
|
|
6
|
+
import { stepLanguage, stepProjectType, stepProviderSelection, stepEditorSelection, stepPrimaryProvider, stepModelSelection, stepExecutionProfile, stepConfirmation, stepTermsOfUse } from './questions.js';
|
|
7
7
|
import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
8
8
|
import { installFramework } from '../installer/core.js';
|
|
9
|
+
import { INSTALLATION_ARTIFACT_PATH, dryRunV2, installV2, reconfigureV2 } from '../installer-v2/index.js';
|
|
10
|
+
import { buildWizardV2InstallationInput } from '../installer-v2/wizard-installation.js';
|
|
11
|
+
import { resolveCapabilityCatalog, verifySignedCapabilityCatalog } from '../installer-v2/catalog-client.js';
|
|
9
12
|
import { validateInstallation } from '../installer/validator.js';
|
|
10
13
|
import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
|
|
11
14
|
import { sendEvents } from '../telemetry/sender.js';
|
|
@@ -69,11 +72,25 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
69
72
|
// Step 3b: Primary provider (only if multiple)
|
|
70
73
|
const primaryProvider = options.llmProvider || await stepPrimaryProvider(selectedProviders);
|
|
71
74
|
|
|
72
|
-
// Step 3c:
|
|
75
|
+
// Step 3c: Models allowed for each selected provider. Never add a provider
|
|
76
|
+
// or model that the user did not explicitly bind to this installation.
|
|
77
|
+
if (options.capabilityCatalog) {
|
|
78
|
+
throw Object.assign(new Error('unsigned capabilityCatalog injection is forbidden; provide signedCapabilityCatalogEnvelope'), { code: 'UNSIGNED_CATALOG_FORBIDDEN' });
|
|
79
|
+
}
|
|
80
|
+
const catalogResolution = options.signedCapabilityCatalogEnvelope
|
|
81
|
+
? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
|
|
82
|
+
: await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
|
|
83
|
+
const modelSelections = options.modelSelections || await stepModelSelection(selectedProviders, catalogResolution.catalog);
|
|
84
|
+
|
|
85
|
+
// Step 3d: Profile controls whether ClickUp and internal completion metrics
|
|
86
|
+
// are mandatory. It must never be silently forced on public installations.
|
|
87
|
+
const executionProfile = options.executionProfile || await stepExecutionProfile();
|
|
88
|
+
|
|
89
|
+
// Step 3e: Editor Selection (which editor IDEs get rules files)
|
|
73
90
|
const selectedEditors = options.editors || await stepEditorSelection();
|
|
74
91
|
|
|
75
92
|
// Combine providers + editors into selectedIDEs for backward compatibility
|
|
76
|
-
const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli' };
|
|
93
|
+
const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli', grok: 'grok-cli' };
|
|
77
94
|
const selectedIDEs = options.ides || [
|
|
78
95
|
...selectedProviders.map(p => providerToIDE[p]).filter(Boolean),
|
|
79
96
|
...selectedEditors,
|
|
@@ -94,9 +111,25 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
94
111
|
allProviders: cliProviders,
|
|
95
112
|
selectedIDEs,
|
|
96
113
|
selectedMCPs,
|
|
114
|
+
executionProfile,
|
|
97
115
|
targetDir,
|
|
98
116
|
version: VERSION,
|
|
99
117
|
};
|
|
118
|
+
const v2InstallationInput = buildWizardV2InstallationInput({
|
|
119
|
+
projectName,
|
|
120
|
+
selectedProviders: cliProviders,
|
|
121
|
+
primaryProvider,
|
|
122
|
+
modelSelections,
|
|
123
|
+
capabilityCatalog: catalogResolution.catalog,
|
|
124
|
+
profile: executionProfile,
|
|
125
|
+
});
|
|
126
|
+
// Validate the complete v2 material and refuse a repeated init before the
|
|
127
|
+
// legacy installer writes anything. Reconfiguration uses its own backup and
|
|
128
|
+
// atomic replacement path below.
|
|
129
|
+
dryRunV2(v2InstallationInput, { projectDir: targetDir });
|
|
130
|
+
if (existsSync(join(targetDir, INSTALLATION_ARTIFACT_PATH)) && options.reconfigure !== true) {
|
|
131
|
+
throw Object.assign(new Error('a v2 installation already exists; use --reconfigure to replace it safely'), { code: 'INSTALLATION_ALREADY_EXISTS' });
|
|
132
|
+
}
|
|
100
133
|
|
|
101
134
|
await stepConfirmation(config);
|
|
102
135
|
|
|
@@ -115,6 +148,8 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
115
148
|
|
|
116
149
|
try {
|
|
117
150
|
await installFramework(config);
|
|
151
|
+
if (options.reconfigure === true) reconfigureV2(v2InstallationInput, { projectDir: targetDir });
|
|
152
|
+
else installV2(v2InstallationInput, { projectDir: targetDir });
|
|
118
153
|
installSpinner.stop();
|
|
119
154
|
|
|
120
155
|
showStep(t('installer.created_chati'));
|
package/src/wizard/questions.js
CHANGED
|
@@ -5,6 +5,7 @@ import { showSummary, showChecklist } from './feedback.js';
|
|
|
5
5
|
import { brand, dim, success } from '../utils/colors.js';
|
|
6
6
|
import { isProviderAvailable } from '../terminal/cli-registry.js';
|
|
7
7
|
import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
|
|
8
|
+
import { supportedV2WizardProviders, wizardDefaultModels, wizardModelSuggestions } from '../installer-v2/wizard-installation.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Step 1: Language Selection (always in English)
|
|
@@ -66,6 +67,7 @@ const PROVIDER_DISPLAY_NAMES = {
|
|
|
66
67
|
claude: 'Claude (Anthropic)',
|
|
67
68
|
gemini: 'Gemini (Google)',
|
|
68
69
|
codex: 'Codex (OpenAI)',
|
|
70
|
+
grok: 'Grok (xAI)',
|
|
69
71
|
};
|
|
70
72
|
|
|
71
73
|
/**
|
|
@@ -75,7 +77,7 @@ const PROVIDER_DISPLAY_NAMES = {
|
|
|
75
77
|
* Returns an array of provider names (e.g. ['claude', 'gemini']).
|
|
76
78
|
*/
|
|
77
79
|
export async function stepProviderSelection() {
|
|
78
|
-
const cliIDEs = Object.entries(IDE_CONFIGS).filter(([, c]) => c.group === 'cli');
|
|
80
|
+
const cliIDEs = Object.entries(IDE_CONFIGS).filter(([key, c]) => c.group === 'cli' && supportedV2WizardProviders.includes(IDE_TO_PROVIDER[key]));
|
|
79
81
|
const options = [];
|
|
80
82
|
|
|
81
83
|
for (const [key, config] of cliIDEs) {
|
|
@@ -110,6 +112,26 @@ export async function stepProviderSelection() {
|
|
|
110
112
|
return selected;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Selects the operational policy profile. Focus AI internal projects require
|
|
117
|
+
* ClickUp tracking; standard projects do not gain that external dependency.
|
|
118
|
+
*/
|
|
119
|
+
export async function stepExecutionProfile() {
|
|
120
|
+
const profile = await p.select({
|
|
121
|
+
message: t('installer.execution_profile_title'),
|
|
122
|
+
options: [
|
|
123
|
+
{ value: 'other-supported-profile', label: t('installer.execution_profile_standard'), hint: t('installer.execution_profile_standard_hint') },
|
|
124
|
+
{ value: 'focus-ai-internal', label: t('installer.execution_profile_internal'), hint: t('installer.execution_profile_internal_hint') },
|
|
125
|
+
],
|
|
126
|
+
initialValue: 'other-supported-profile',
|
|
127
|
+
});
|
|
128
|
+
if (p.isCancel(profile)) {
|
|
129
|
+
p.cancel('Installation cancelled.');
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
return profile;
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
/**
|
|
114
136
|
* Step 3b: Editor Selection (multi-select, optional)
|
|
115
137
|
*
|
|
@@ -228,11 +250,43 @@ export async function stepPrimaryProvider(cliProviders) {
|
|
|
228
250
|
return primary;
|
|
229
251
|
}
|
|
230
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Records exactly which models the installation is allowed to orchestrate.
|
|
255
|
+
* A catalog refresh can replace the suggestion, but the user remains the
|
|
256
|
+
* authority for the final binding stored in the installation artifact.
|
|
257
|
+
*/
|
|
258
|
+
export async function stepModelSelection(providers, capabilityCatalog) {
|
|
259
|
+
const selections = {};
|
|
260
|
+
|
|
261
|
+
for (const provider of providers) {
|
|
262
|
+
const suggestions = wizardModelSuggestions(provider, capabilityCatalog);
|
|
263
|
+
const suggested = suggestions[0] || wizardDefaultModels[provider];
|
|
264
|
+
const answer = await p.text({
|
|
265
|
+
message: `Models for ${PROVIDER_DISPLAY_NAMES[provider] || provider} (comma-separated):`,
|
|
266
|
+
placeholder: suggestions.join(', '),
|
|
267
|
+
initialValue: suggested,
|
|
268
|
+
validate: (value) => {
|
|
269
|
+
const models = String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
|
|
270
|
+
return models.length > 0 ? undefined : 'Select at least one model.';
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (p.isCancel(answer)) {
|
|
275
|
+
p.cancel('Installation cancelled.');
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
selections[provider] = String(answer).split(',').map((item) => item.trim()).filter(Boolean);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return selections;
|
|
283
|
+
}
|
|
284
|
+
|
|
231
285
|
/**
|
|
232
286
|
* Step 4: Confirmation
|
|
233
287
|
*/
|
|
234
288
|
export async function stepConfirmation(config) {
|
|
235
|
-
const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders } = config;
|
|
289
|
+
const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders, executionProfile } = config;
|
|
236
290
|
|
|
237
291
|
const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
|
|
238
292
|
|
|
@@ -256,6 +310,9 @@ export async function stepConfirmation(config) {
|
|
|
256
310
|
[t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
|
|
257
311
|
[t('installer.language_label')]: langName,
|
|
258
312
|
[t('installer.providers_label')]: providersDisplay,
|
|
313
|
+
[t('installer.execution_profile_label')]: executionProfile === 'focus-ai-internal'
|
|
314
|
+
? `${t('installer.execution_profile_internal')} (ClickUp required)`
|
|
315
|
+
: t('installer.execution_profile_standard'),
|
|
259
316
|
};
|
|
260
317
|
|
|
261
318
|
// Only show editor IDEs line if any were selected
|