chati-dev 4.5.28 → 4.5.29
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/bin/chati.js +151 -123
- package/framework/agents/build/dev.md +4 -4
- package/framework/agents/deploy/devops.md +3 -3
- package/framework/agents/discover/brief.md +18 -6
- package/framework/agents/discover/brownfield-wu.md +3 -3
- package/framework/agents/discover/greenfield-wu.md +4 -4
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +4 -4
- package/framework/agents/plan/phases.md +3 -3
- package/framework/agents/plan/tasks.md +47 -19
- package/framework/agents/plan/ux-brand-architect.md +1 -1
- package/framework/agents/plan/ux.md +26 -2
- package/framework/agents/quality/qa-implementation.md +8 -8
- package/framework/agents/quality/qa-planning.md +58 -4
- package/framework/agents/quality/qa-visual.md +4 -4
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +12 -1
- package/framework/context/governance.md +2 -1
- package/framework/context/root.md +3 -1
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/global.yaml +1 -1
- package/framework/domains/workflows/standard-flow.yaml +7 -8
- package/framework/hooks/advance-trigger.js +3 -2
- package/framework/hooks/brief-validator.js +1 -1
- package/framework/hooks/post-dev.js +1 -1
- package/framework/hooks/team-quality-gate.js +9 -2
- package/framework/i18n/en.yaml +1 -0
- package/framework/i18n/es.yaml +1 -0
- package/framework/i18n/fr.yaml +1 -0
- package/framework/i18n/pt.yaml +1 -0
- package/framework/manifest.json +83 -83
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +14 -6
- package/framework/orchestrator/chati.md +61 -23
- package/framework/rules/human-writing-style.md +7 -0
- package/framework/schemas/session.schema.json +25 -2
- package/framework/schemas/task.schema.json +26 -0
- package/framework/tasks/orchestrator-handoff.md +10 -10
- package/framework/tasks/orchestrator-resume.md +3 -3
- package/framework/workflows/brownfield-fullstack.yaml +6 -3
- package/framework/workflows/brownfield-service.yaml +5 -3
- package/framework/workflows/brownfield-ui.yaml +5 -3
- package/framework/workflows/greenfield-fullstack.yaml +6 -3
- package/framework/workflows/standard-flow.yaml +8 -3
- package/node_modules/@chati/core/native/browser-proof-wire.h +29 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.c +36 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.h +32 -0
- package/node_modules/@chati/core/native/darwin-browser-proof.h +86 -0
- package/node_modules/@chati/core/native/darwin-code-identity.h +35 -0
- package/node_modules/@chati/core/native/darwin-launch-messages.h +37 -0
- package/node_modules/@chati/core/native/darwin-launcher.c +312 -0
- package/node_modules/@chati/core/native/descriptor-fs.c +407 -0
- package/node_modules/@chati/core/native/execution-admission.h +59 -0
- package/node_modules/@chati/core/native/execution-gate.c +41 -0
- package/node_modules/@chati/core/native/linux-process.c +41 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/darwin-launcher +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/execution-gate +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/execution-gate +0 -0
- package/node_modules/@chati/core/native/process-info-pipe.c +91 -0
- package/node_modules/@chati/core/package.json +2 -1
- package/node_modules/@chati/core/scripts/build-native.js +36 -0
- package/node_modules/@chati/core/src/darwin-browser-proof.js +87 -0
- package/node_modules/@chati/core/src/darwin-host-boot.js +58 -0
- package/node_modules/@chati/core/src/darwin-process.js +79 -0
- package/node_modules/@chati/core/src/darwin-supervisor-channels.js +82 -0
- package/node_modules/@chati/core/src/darwin-terminal-receipt.js +111 -0
- package/node_modules/@chati/core/src/descriptor-fs.js +222 -0
- package/node_modules/@chati/core/src/execution-gate.js +8 -0
- package/node_modules/@chati/core/src/host-boot-marker.js +45 -0
- package/node_modules/@chati/core/src/index.js +18 -0
- package/node_modules/@chati/core/src/linux-host-boot.js +218 -0
- package/node_modules/@chati/core/src/linux-process.js +133 -0
- package/node_modules/@chati/planning/src/browser-verification-plan.js +71 -0
- package/node_modules/@chati/planning/src/index.js +33 -0
- package/node_modules/@chati/planning/src/planning-material.js +64 -0
- package/node_modules/@chati/planning/src/verification-plan.js +57 -0
- package/node_modules/@chati/rail/package.json +1 -0
- package/node_modules/@chati/rail/src/execution-restoration.js +92 -0
- package/node_modules/@chati/rail/src/index.js +812 -167
- package/node_modules/@chati/rail/src/provider-processing.js +118 -0
- package/node_modules/@chati/rail/src/terminal-containment.js +23 -0
- package/node_modules/@chati/release-lane/src/index.js +40 -43
- package/node_modules/@chati/review-council/src/index.js +223 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +67 -58
- package/package-artifact-manifest.json +1 -1
- package/package-artifact-manifest.sig +1 -1
- package/package.json +3 -2
- package/scripts/verify-real-harness-e2e.js +20 -8
- package/src/installer/core.js +147 -107
- package/src/installer/package-artifact.js +7 -0
- package/src/installer/preflight.js +13 -0
- package/src/installer/provider-overlay.js +1 -2
- package/src/installer/templates.js +58 -0
- package/src/installer-v2/catalog-client.js +31 -1
- package/src/installer-v2/clickup-connection-storage.js +92 -0
- package/src/installer-v2/clickup-mcp-client.js +155 -0
- package/src/installer-v2/clickup-oauth-loopback.js +96 -0
- package/src/installer-v2/clickup-oauth.js +233 -0
- package/src/installer-v2/index.js +141 -15
- package/src/installer-v2/installation-authority.js +98 -4
- package/src/installer-v2/provider-executable.js +117 -12
- package/src/installer-v2/wizard-installation.js +11 -0
- package/src/orchestrator/agent-selector.js +39 -1
- package/src/orchestrator/browser-artifact-store.js +131 -0
- package/src/orchestrator/cli.js +762 -205
- package/src/orchestrator/clickup-mcp-observer.js +226 -0
- package/src/orchestrator/clickup-runtime.js +45 -24
- package/src/orchestrator/council-gate-runtime.js +903 -0
- package/src/orchestrator/evidence-authority-enrollment.js +75 -0
- package/src/orchestrator/evidence-authority-request.js +46 -0
- package/src/orchestrator/handoff-engine.js +4 -11
- package/src/orchestrator/pipeline-manager.js +42 -4
- package/src/orchestrator/planning-contract-revision.js +711 -0
- package/src/orchestrator/planning-reconciliation.js +302 -0
- package/src/orchestrator/planning-runtime.js +476 -29
- package/src/orchestrator/rail-adjudication-evidence.js +15 -24
- package/src/orchestrator/rail-human-effort-authority.js +46 -0
- package/src/orchestrator/rail-human-effort.js +149 -0
- package/src/orchestrator/rail-runtime.js +291 -47
- package/src/orchestrator/rail-verification.js +278 -0
- package/src/orchestrator/review-runtime.js +37 -2
- package/src/orchestrator/runtime-installation-v2.js +143 -24
- package/src/orchestrator/session-manager.js +1457 -29
- package/src/tasks/handoff.js +57 -33
- package/src/terminal/adapters/claude-adapter.js +3 -0
- package/src/terminal/adapters/codex-adapter.js +13 -0
- package/src/terminal/adapters/grok-adapter.js +1 -0
- package/src/terminal/agent-attempt-budget.js +35 -0
- package/src/terminal/browser-endpoint-proxy.js +174 -0
- package/src/terminal/browser-observer-driver.js +94 -0
- package/src/terminal/browser-observer-supervisor.js +73 -0
- package/src/terminal/browser-observer.js +95 -0
- package/src/terminal/browser-process-facts.js +75 -0
- package/src/terminal/browser-runtime.js +131 -0
- package/src/terminal/darwin-authority-policy.js +213 -0
- package/src/terminal/darwin-browser-application.js +116 -0
- package/src/terminal/darwin-browser-observer.js +57 -0
- package/src/terminal/darwin-execution.js +185 -0
- package/src/terminal/executable-snapshot.js +202 -0
- package/src/terminal/handoff-parser.js +48 -2
- package/src/terminal/isolation.js +14 -13
- package/src/terminal/prompt-builder.js +176 -15
- package/src/terminal/provider-preflight.js +47 -27
- package/src/terminal/rail-execution-worktree.js +22 -6
- package/src/terminal/rail-process-observer.js +10 -0
- package/src/terminal/rail-prompts.js +21 -3
- package/src/terminal/rail-readonly-workspace.js +54 -5
- package/src/terminal/rail-worktree-checkpoint.js +315 -0
- package/src/terminal/rail-worktree-restoration.js +485 -0
- package/src/terminal/run-agent.js +164 -42
- package/src/terminal/run-council-gate.js +338 -0
- package/src/terminal/run-planning-reconciliation.js +49 -0
- package/src/terminal/run-rail-adjudication.js +29 -7
- package/src/terminal/run-rail-recovery.js +97 -0
- package/src/terminal/run-rail-review.js +36 -15
- package/src/terminal/run-rail-rework.js +26 -15
- package/src/terminal/run-rail-task.js +22 -9
- package/src/terminal/runtime-native-helper.js +85 -0
- package/src/terminal/spawner.js +899 -142
- package/src/terminal/verification-process-facts.js +49 -0
- package/src/upgrade/entrypoints.js +68 -0
- package/src/upgrade/generation.js +194 -0
- package/src/upgrade/maintenance.js +79 -0
- package/src/upgrade/paths.js +81 -0
- package/src/upgrade/upgrade.js +267 -0
- package/src/utils/execution-role.js +15 -0
- package/src/utils/handoff-paths.js +59 -0
- package/src/wizard/i18n.js +1 -0
- package/src/wizard/index.js +17 -5
- package/src/wizard/questions.js +24 -12
package/src/installer/core.js
CHANGED
|
@@ -5,14 +5,16 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
|
|
7
7
|
import { mergeClaudeMCPConfig, upsertManagedMcpToml } from '../config/mcp-configs.js';
|
|
8
|
-
import { generateSessionYaml, generateConfigYaml, generateProjectContext, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGrokRouter, generateGrokGovernanceRules, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
|
|
8
|
+
import { generateSessionYaml, generateConfigYaml, generateProjectContext, generateClaudeMd, generateClaudeLocalMd, generateClaudeRouter, generateCodexSkill, generateGeminiRouter, generateGrokRouter, generateGrokGovernanceRules, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
|
|
9
9
|
import { generateContextFiles } from '../config/context-file-generator.js';
|
|
10
10
|
import { adaptFrameworkFile, ADAPTABLE_FILES, stripLegacyBuildSurface } from '../config/framework-adapter.js';
|
|
11
11
|
import { generateClaudeSettings } from '../config/claude-settings-generator.js';
|
|
12
12
|
import { generateProviderOverlays } from './provider-overlay.js';
|
|
13
13
|
import { applyInstalledPathReplacement } from './path-replacement.js';
|
|
14
14
|
import { verifyFrameworkSource } from './manifest.js';
|
|
15
|
+
import { PACKAGE_ARTIFACT_MANIFEST, PACKAGE_ARTIFACT_SIGNATURE } from './package-artifact.js';
|
|
15
16
|
import { initializeSessionDocument } from '../orchestrator/session-manager.js';
|
|
17
|
+
import { withUpgradeMaintenance } from '../upgrade/maintenance.js';
|
|
16
18
|
import * as yaml from 'js-yaml';
|
|
17
19
|
|
|
18
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -55,6 +57,14 @@ export async function installFramework(config) {
|
|
|
55
57
|
);
|
|
56
58
|
if (initialSession.error) throw new Error(initialSession.error);
|
|
57
59
|
|
|
60
|
+
// The first session creates .chati. Hold the lease from this point through
|
|
61
|
+
// all framework/configuration writes, including direct installer API use.
|
|
62
|
+
return withUpgradeMaintenance(targetDir, () => installPreparedFramework(config, enabledProviders, version));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function installPreparedFramework(config, enabledProviders, version) {
|
|
66
|
+
const { targetDir, projectType, language, selectedIDEs, selectedMCPs } = config;
|
|
67
|
+
|
|
58
68
|
// 2. Create .chati.dev/ framework directory (hidden — dot prefix)
|
|
59
69
|
// The source is chati.dev/ in the monorepo; installed as .chati.dev/ in user projects.
|
|
60
70
|
// Text replacement during copy handles all internal path references.
|
|
@@ -120,17 +130,61 @@ export async function installFramework(config) {
|
|
|
120
130
|
createDir(join(memoriesBase, dir));
|
|
121
131
|
}
|
|
122
132
|
|
|
133
|
+
copyInstalledRuntime({ frameworkDir, providers: enabledProviders, runtimeVersion: config.runtimeVersion, version });
|
|
134
|
+
|
|
135
|
+
// Write config.yaml
|
|
136
|
+
writeFileSync(
|
|
137
|
+
join(frameworkDir, 'config.yaml'),
|
|
138
|
+
generateConfigYaml({
|
|
139
|
+
version, projectType, language, selectedIDEs,
|
|
140
|
+
allProviders: enabledProviders,
|
|
141
|
+
runtimeVersion: config.runtimeVersion,
|
|
142
|
+
installationMode: config.installationMode,
|
|
143
|
+
clickupPreflight: config.clickupPreflight,
|
|
144
|
+
}),
|
|
145
|
+
'utf-8'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// 3. Configure every CLI against its own overlay. The CLI that opens a
|
|
149
|
+
// session becomes that session's orchestrator.
|
|
150
|
+
for (const ideKey of selectedIDEs) {
|
|
151
|
+
const ideProvider = IDE_TO_PROVIDER[ideKey];
|
|
152
|
+
const hasProviderOverlay = ideProvider && enabledProviders.includes(ideProvider);
|
|
153
|
+
const orchestratorPath = hasProviderOverlay
|
|
154
|
+
? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
|
|
155
|
+
: '.chati.dev/orchestrator/chati.md';
|
|
156
|
+
await configureIDE(targetDir, ideKey, selectedMCPs, {
|
|
157
|
+
orchestratorPath,
|
|
158
|
+
isSecondary: false,
|
|
159
|
+
providerName: ideProvider,
|
|
160
|
+
runtimeVersion: config.runtimeVersion,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
await finishProjectInstallation(config);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Assemble only the managed runtime. Upgrade can reuse this in private staging
|
|
168
|
+
* without regenerating project context, session, bindings or CLAUDE.md. */
|
|
169
|
+
export function copyInstalledRuntime({ frameworkDir, providers = [], runtimeVersion, version, strict = false }) {
|
|
170
|
+
const isRuntimeV2 = runtimeVersion === 2;
|
|
171
|
+
const packageRoot = join(__dirname, '..', '..');
|
|
172
|
+
const packageVersion = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')).version;
|
|
173
|
+
if (strict && version !== packageVersion) {
|
|
174
|
+
throw Object.assign(new Error('Runtime version must match the package being copied'), { code: 'UPGRADE_PACKAGE_VERSION_MISMATCH' });
|
|
175
|
+
}
|
|
176
|
+
const options = { strict };
|
|
123
177
|
// Copy the provider-neutral canonical framework. Every enabled CLI receives
|
|
124
178
|
// an explicit native overlay below, but no provider owns the source core.
|
|
125
|
-
copyFrameworkFiles(frameworkDir, null);
|
|
179
|
+
copyFrameworkFiles(frameworkDir, null, options);
|
|
126
180
|
|
|
127
181
|
// Generate symmetric provider overlays. No CLI is permanently primary.
|
|
128
|
-
if (
|
|
129
|
-
generateProviderOverlays(
|
|
182
|
+
if (providers.length > 0) {
|
|
183
|
+
generateProviderOverlays(dirname(frameworkDir), FRAMEWORK_SOURCE, providers, { frameworkDir });
|
|
130
184
|
}
|
|
131
185
|
|
|
132
186
|
if (isRuntimeV2) {
|
|
133
|
-
pruneLegacyExecutionSurface(frameworkDir,
|
|
187
|
+
pruneLegacyExecutionSurface(frameworkDir, providers);
|
|
134
188
|
pruneLegacyRegistryEntities(frameworkDir);
|
|
135
189
|
}
|
|
136
190
|
|
|
@@ -140,7 +194,8 @@ export async function installFramework(config) {
|
|
|
140
194
|
// Result: every install is self-contained; no network/cache dependency at runtime.
|
|
141
195
|
copyCliSource(
|
|
142
196
|
join(__dirname, '..'), // packages/chati-dev/src/
|
|
143
|
-
join(frameworkDir, '_cli')
|
|
197
|
+
join(frameworkDir, '_cli'), // .chati.dev/_cli/
|
|
198
|
+
options
|
|
144
199
|
);
|
|
145
200
|
|
|
146
201
|
// The bundled CLI imports ../../framework/hooks/model-governance.js relative
|
|
@@ -149,7 +204,7 @@ export async function installFramework(config) {
|
|
|
149
204
|
// .chati.dev/framework/hooks/ to satisfy the import path.
|
|
150
205
|
copyDirVerbatim(
|
|
151
206
|
join(__dirname, '..', '..', 'framework', 'hooks'),
|
|
152
|
-
join(frameworkDir, 'framework', 'hooks')
|
|
207
|
+
join(frameworkDir, 'framework', 'hooks'), options
|
|
153
208
|
);
|
|
154
209
|
if (isRuntimeV2) {
|
|
155
210
|
rmSync(join(frameworkDir, 'framework', 'hooks', 'post-dev.js'), { force: true });
|
|
@@ -159,47 +214,34 @@ export async function installFramework(config) {
|
|
|
159
214
|
// without relying on a node_modules tree in the user's project.
|
|
160
215
|
copyCliDependencies(
|
|
161
216
|
join(__dirname, '..', '..'), // packages/chati-dev/
|
|
162
|
-
join(frameworkDir, '_cli', 'node_modules')
|
|
217
|
+
join(frameworkDir, '_cli', 'node_modules'), // .chati.dev/_cli/node_modules/
|
|
218
|
+
options
|
|
163
219
|
);
|
|
164
220
|
|
|
221
|
+
// Preserve the publisher's original attestation for native runtime helpers.
|
|
222
|
+
// Their installed bytes must be checked against this signature, never blessed
|
|
223
|
+
// by hashing the installed file itself. The runtime reader verifies it before
|
|
224
|
+
// preparing any executable snapshot. Missing metadata must not stamp success.
|
|
225
|
+
for (const name of [PACKAGE_ARTIFACT_MANIFEST, PACKAGE_ARTIFACT_SIGNATURE]) {
|
|
226
|
+
const source = join(packageRoot, name);
|
|
227
|
+
const stat = lstatSync(source);
|
|
228
|
+
if (!stat.isFile() || stat.nlink !== 1) unsafeManagedPath(source, 'package attestation must be a single-link regular file');
|
|
229
|
+
copySafeManagedFile(source, join(frameworkDir, '_cli', name));
|
|
230
|
+
}
|
|
231
|
+
|
|
165
232
|
// v4.3.1+ — Write version stamp so chati-router.js can detect bundle/canonical
|
|
166
233
|
// mismatch on subsequent invocations. Pre-v4.3.1 installs lack this file;
|
|
167
234
|
// mismatch detection silent-skips when it's missing (graceful degradation).
|
|
168
235
|
writeFileSync(
|
|
169
236
|
join(frameworkDir, '_cli', 'version.json'),
|
|
170
|
-
JSON.stringify({ version, bundled_at: new Date().toISOString() }, null, 2),
|
|
237
|
+
JSON.stringify({ version, package_version: packageVersion, bundled_at: new Date().toISOString(), maintenance_protocol: 1 }, null, 2),
|
|
171
238
|
'utf-8'
|
|
172
239
|
);
|
|
173
240
|
|
|
174
|
-
|
|
175
|
-
writeFileSync(
|
|
176
|
-
join(frameworkDir, 'config.yaml'),
|
|
177
|
-
generateConfigYaml({
|
|
178
|
-
version, projectType, language, selectedIDEs,
|
|
179
|
-
allProviders: enabledProviders,
|
|
180
|
-
runtimeVersion: config.runtimeVersion,
|
|
181
|
-
installationMode: config.installationMode,
|
|
182
|
-
clickupPreflight: config.clickupPreflight,
|
|
183
|
-
}),
|
|
184
|
-
'utf-8'
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// 3. Configure every CLI against its own overlay. The CLI that opens a
|
|
188
|
-
// session becomes that session's orchestrator.
|
|
189
|
-
for (const ideKey of selectedIDEs) {
|
|
190
|
-
const ideProvider = IDE_TO_PROVIDER[ideKey];
|
|
191
|
-
const hasProviderOverlay = ideProvider && enabledProviders.includes(ideProvider);
|
|
192
|
-
const orchestratorPath = hasProviderOverlay
|
|
193
|
-
? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
|
|
194
|
-
: '.chati.dev/orchestrator/chati.md';
|
|
195
|
-
await configureIDE(targetDir, ideKey, selectedMCPs, {
|
|
196
|
-
orchestratorPath,
|
|
197
|
-
isSecondary: false,
|
|
198
|
-
providerName: ideProvider,
|
|
199
|
-
runtimeVersion: config.runtimeVersion,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
241
|
+
}
|
|
202
242
|
|
|
243
|
+
async function finishProjectInstallation(config) {
|
|
244
|
+
const { targetDir, projectName, projectType, language, selectedIDEs } = config;
|
|
203
245
|
const hasClaude = selectedIDEs.includes('claude-code');
|
|
204
246
|
// 4. Generate the provider-neutral project context. Harness files consume
|
|
205
247
|
// this artifact and never become the source for one another.
|
|
@@ -398,8 +440,8 @@ const TEXT_EXTENSIONS = new Set(['.md', '.js', '.yaml', '.yml', '.json', '.html'
|
|
|
398
440
|
* 1. Path replacement (chati.dev/ → .chati.dev/, artifacts/ at root)
|
|
399
441
|
* 2. Provider adaptation for non-Claude providers
|
|
400
442
|
*/
|
|
401
|
-
function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
402
|
-
if (!existsSync(srcDir)) return;
|
|
443
|
+
function copyDirRecursive(srcDir, destDir, provider, relBase = '', options = {}) {
|
|
444
|
+
if (!existsSync(srcDir) && !options.strict) return;
|
|
403
445
|
createSafeManagedDirectory(destDir);
|
|
404
446
|
|
|
405
447
|
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
@@ -411,7 +453,7 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
|
411
453
|
if (entry.isDirectory()) {
|
|
412
454
|
// Skip artifacts/ — those live at project root, not inside .chati.dev/
|
|
413
455
|
if (entry.name === 'artifacts') continue;
|
|
414
|
-
copyDirRecursive(srcPath, destPath, provider, relPath);
|
|
456
|
+
copyDirRecursive(srcPath, destPath, provider, relPath, options);
|
|
415
457
|
} else if (entry.isFile()) {
|
|
416
458
|
const ext = entry.name.includes('.') ? '.' + entry.name.split('.').pop() : '';
|
|
417
459
|
const isText = TEXT_EXTENSIONS.has(ext);
|
|
@@ -429,7 +471,7 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
|
429
471
|
// Binary file: direct copy
|
|
430
472
|
copySafeManagedFile(srcPath, destPath);
|
|
431
473
|
}
|
|
432
|
-
}
|
|
474
|
+
} else if (options.strict) unsafeManagedPath(srcPath, 'source must contain only regular files and directories');
|
|
433
475
|
}
|
|
434
476
|
}
|
|
435
477
|
|
|
@@ -442,13 +484,13 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
|
|
|
442
484
|
* @param {string} destDir - Target framework directory (e.g., {project}/chati.dev/)
|
|
443
485
|
* @param {string} provider - Primary provider for text adaptation ('claude' | 'gemini' | 'codex')
|
|
444
486
|
*/
|
|
445
|
-
export function copyFrameworkFiles(destDir, provider = null) {
|
|
446
|
-
if (!existsSync(FRAMEWORK_SOURCE)) return;
|
|
487
|
+
export function copyFrameworkFiles(destDir, provider = null, options = {}) {
|
|
488
|
+
if (!existsSync(FRAMEWORK_SOURCE) && !options.strict) return;
|
|
447
489
|
|
|
448
490
|
// Copy loose root files (with path replacement)
|
|
449
491
|
for (const file of FRAMEWORK_ROOT_FILES) {
|
|
450
492
|
const src = join(FRAMEWORK_SOURCE, file);
|
|
451
|
-
if (!existsSync(src)) continue;
|
|
493
|
+
if (!existsSync(src) && !options.strict) continue;
|
|
452
494
|
const dest = join(destDir, file);
|
|
453
495
|
createSafeManagedDirectory(dirname(dest));
|
|
454
496
|
let content = readFileSync(src, 'utf-8');
|
|
@@ -463,7 +505,7 @@ export function copyFrameworkFiles(destDir, provider = null) {
|
|
|
463
505
|
for (const dir of FRAMEWORK_DIRS_TO_COPY) {
|
|
464
506
|
const srcDir = join(FRAMEWORK_SOURCE, dir);
|
|
465
507
|
const destSubDir = join(destDir, dir);
|
|
466
|
-
copyDirRecursive(srcDir, destSubDir, provider, dir);
|
|
508
|
+
copyDirRecursive(srcDir, destSubDir, provider, dir, options);
|
|
467
509
|
}
|
|
468
510
|
}
|
|
469
511
|
|
|
@@ -480,47 +522,7 @@ async function configureIDE(targetDir, ideKey, selectedMCPs, options = {}) {
|
|
|
480
522
|
|
|
481
523
|
if (ideKey === 'claude-code') {
|
|
482
524
|
// Thin router
|
|
483
|
-
const routerContent =
|
|
484
|
-
|
|
485
|
-
## STEP 0 — Run the deterministic pipeline router (MUST be first)
|
|
486
|
-
|
|
487
|
-
Run via Bash:
|
|
488
|
-
\`\`\`
|
|
489
|
-
node .chati.dev/orchestrator/chati-router.js --harness claude
|
|
490
|
-
\`\`\`
|
|
491
|
-
|
|
492
|
-
Parse the JSON output. This single command already handled: license validation, integrity check, session state, pipeline routing, and team registration (if needed). You do NOT need to run any of these separately.
|
|
493
|
-
|
|
494
|
-
### If \`ok\` is false:
|
|
495
|
-
|
|
496
|
-
| \`error\` value | What to tell the user |
|
|
497
|
-
|---|---|
|
|
498
|
-
| \`license_invalid\` | Display \`license.reason\` as a friendly message. STOP. |
|
|
499
|
-
| \`license_guard_missing\` | "License enforcement is missing. Reinstall with: npx chati-dev init". STOP. |
|
|
500
|
-
| \`integrity_failed\` | "License enforcement was tampered with. Reinstall with: npx chati-dev init". STOP. |
|
|
501
|
-
| anything else | Display error. STOP. |
|
|
502
|
-
|
|
503
|
-
### If \`ok\` is true:
|
|
504
|
-
|
|
505
|
-
1. Use \`session.language\` for every visible response, preface, progress message, question and error, starting with the first response (pt = Português, en = English, es = Español, fr = Français).
|
|
506
|
-
2. Read the full orchestrator at \`${orchestratorPath || '.chati.dev/orchestrator/chati.md'}\`.
|
|
507
|
-
3. The \`action\` field tells you what to do. Go to the matching "Action:" section in the orchestrator.
|
|
508
|
-
4. **If \`action\` is \`spawn_team\`**: the team is ALREADY registered (Step 1 already ran as code). The \`team\` object contains \`team_id\`, \`task_list_path\`, \`mailbox_path\`, \`members\`. Go directly to "Action: Spawn Team" Step 2 and use these values in the agent prompts. DO NOT run \`orchestrate spawn-team\` again — it already ran.
|
|
509
|
-
5. **If \`action\` is \`setup\`**: this is a new project. Follow "Action: Setup".
|
|
510
|
-
6. For any other action: follow the matching section in the orchestrator.
|
|
511
|
-
|
|
512
|
-
**The \`pipeline\` object contains the full JSON from \`orchestrate next\`.** Use its fields (agent_file, spawn_command, handoff_status, etc.) as documented in the orchestrator.
|
|
513
|
-
|
|
514
|
-
**Pending interaction invariant:** If \`action\` is \`resume_pending_interaction\`,
|
|
515
|
-
do not execute a provider command. Display exactly \`pipeline.question\`, with
|
|
516
|
-
no preface, summary or paraphrase. After the user answers, write
|
|
517
|
-
\`{"response":"<exact user response>"}\` to \`pipeline.response_file\` using the
|
|
518
|
-
native file-write tool, never a shell command. Then execute
|
|
519
|
-
\`pipeline.deferred_spawn_command\` exactly once and unchanged. Never execute a
|
|
520
|
-
command merely because a command-shaped field is present.
|
|
521
|
-
|
|
522
|
-
**User input:** $ARGUMENTS
|
|
523
|
-
`;
|
|
525
|
+
const routerContent = generateClaudeRouter({ orchestratorPath });
|
|
524
526
|
writeFileSync(join(targetDir, '.claude', 'commands', 'chati.md'), routerContent, 'utf-8');
|
|
525
527
|
|
|
526
528
|
// .claude/settings.json — wires the 10 chati.dev hooks into Claude Code's
|
|
@@ -1057,8 +1059,8 @@ function copySafeManagedFile(source, destination) {
|
|
|
1057
1059
|
* Used to bundle the CLI source into the user's .chati.dev/_cli/ folder so
|
|
1058
1060
|
* the framework runs self-contained (no `npx chati-dev` dependency).
|
|
1059
1061
|
*/
|
|
1060
|
-
export function copyCliSource(srcDir, destDir) {
|
|
1061
|
-
if (!existsSync(srcDir)) return;
|
|
1062
|
+
export function copyCliSource(srcDir, destDir, options = {}) {
|
|
1063
|
+
if (!existsSync(srcDir) && !options.strict) return;
|
|
1062
1064
|
createSafeManagedDirectory(destDir);
|
|
1063
1065
|
const entries = readdirSync(srcDir);
|
|
1064
1066
|
for (const entry of entries) {
|
|
@@ -1067,10 +1069,12 @@ export function copyCliSource(srcDir, destDir) {
|
|
|
1067
1069
|
const srcPath = join(srcDir, entry);
|
|
1068
1070
|
const destPath = join(destDir, entry);
|
|
1069
1071
|
let stat;
|
|
1070
|
-
try { stat =
|
|
1072
|
+
try { stat = options.strict ? lstatSync(srcPath) : statSync(srcPath); }
|
|
1073
|
+
catch (error) { if (options.strict) throw error; continue; }
|
|
1071
1074
|
if (stat.isDirectory()) {
|
|
1072
|
-
copyCliSource(srcPath, destPath);
|
|
1075
|
+
copyCliSource(srcPath, destPath, options);
|
|
1073
1076
|
} else {
|
|
1077
|
+
if (options.strict && !stat.isFile()) unsafeManagedPath(srcPath, 'CLI source must be a regular file');
|
|
1074
1078
|
copySafeManagedFile(srcPath, destPath);
|
|
1075
1079
|
}
|
|
1076
1080
|
}
|
|
@@ -1079,20 +1083,21 @@ export function copyCliSource(srcDir, destDir) {
|
|
|
1079
1083
|
/**
|
|
1080
1084
|
* Recursively copy a directory verbatim (used for bundling node_modules trees).
|
|
1081
1085
|
*/
|
|
1082
|
-
function copyDirVerbatim(srcDir, destDir) {
|
|
1083
|
-
if (!existsSync(srcDir)) return;
|
|
1086
|
+
function copyDirVerbatim(srcDir, destDir, options = {}) {
|
|
1087
|
+
if (!existsSync(srcDir) && !options.strict) return;
|
|
1084
1088
|
createSafeManagedDirectory(destDir);
|
|
1085
1089
|
for (const entry of readdirSync(srcDir)) {
|
|
1086
1090
|
const srcPath = join(srcDir, entry);
|
|
1087
1091
|
const destPath = join(destDir, entry);
|
|
1088
1092
|
let stat;
|
|
1089
|
-
try { stat = statSync(srcPath); } catch { continue; }
|
|
1093
|
+
try { stat = statSync(srcPath); } catch (error) { if (options.strict) throw error; continue; }
|
|
1090
1094
|
if (stat.isDirectory()) {
|
|
1091
|
-
copyDirVerbatim(srcPath, destPath);
|
|
1095
|
+
copyDirVerbatim(srcPath, destPath, options);
|
|
1092
1096
|
} else {
|
|
1097
|
+
if (options.strict && !stat.isFile()) unsafeManagedPath(srcPath, 'dependency source must be a regular file');
|
|
1093
1098
|
try { copySafeManagedFile(srcPath, destPath); }
|
|
1094
1099
|
catch (error) {
|
|
1095
|
-
if (error?.code === 'UNSAFE_INSTALLATION_TARGET') throw error;
|
|
1100
|
+
if (options.strict || error?.code === 'UNSAFE_INSTALLATION_TARGET') throw error;
|
|
1096
1101
|
/* skip unreadable source material */
|
|
1097
1102
|
}
|
|
1098
1103
|
}
|
|
@@ -1108,11 +1113,11 @@ function copyDirVerbatim(srcDir, destDir) {
|
|
|
1108
1113
|
* which works whether the package is hoisted or nested in the user's
|
|
1109
1114
|
* node_modules (npx cache, npm install, etc.).
|
|
1110
1115
|
*/
|
|
1111
|
-
export function copyCliDependencies(pkgDir, destNodeModules) {
|
|
1116
|
+
export function copyCliDependencies(pkgDir, destNodeModules, options = {}) {
|
|
1112
1117
|
let pkg;
|
|
1113
1118
|
try {
|
|
1114
1119
|
pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
|
|
1115
|
-
} catch { return; }
|
|
1120
|
+
} catch (error) { if (options.strict) throw error; return; }
|
|
1116
1121
|
const deps = [
|
|
1117
1122
|
...Object.keys(pkg.dependencies || {}),
|
|
1118
1123
|
...Object.keys(pkg.optionalDependencies || {}),
|
|
@@ -1121,16 +1126,17 @@ export function copyCliDependencies(pkgDir, destNodeModules) {
|
|
|
1121
1126
|
|
|
1122
1127
|
// Resolve each dep from the package.json location (works for hoisted + nested layouts).
|
|
1123
1128
|
|
|
1124
|
-
const require = createRequire(join(pkgDir, 'package.json'));
|
|
1125
1129
|
createSafeManagedDirectory(destNodeModules);
|
|
1126
1130
|
|
|
1127
1131
|
const visited = new Set();
|
|
1128
|
-
const
|
|
1132
|
+
const resolvedPackages = new Map();
|
|
1133
|
+
const copiedTargets = new Map();
|
|
1134
|
+
const queue = deps.map(name => ({ name, from: pkgDir, destinationParent: dirname(destNodeModules), optional: Object.hasOwn(pkg.optionalDependencies || {}, name) }));
|
|
1129
1135
|
|
|
1130
1136
|
while (queue.length > 0) {
|
|
1131
|
-
const depName = queue.shift();
|
|
1132
|
-
if (visited.has(depName)) continue;
|
|
1133
|
-
|
|
1137
|
+
const { name: depName, optional, from, destinationParent } = queue.shift();
|
|
1138
|
+
if (!options.strict && visited.has(depName)) continue;
|
|
1139
|
+
const require = createRequire(join(options.strict ? from : pkgDir, 'package.json'));
|
|
1134
1140
|
|
|
1135
1141
|
let depDir;
|
|
1136
1142
|
try {
|
|
@@ -1153,19 +1159,53 @@ export function copyCliDependencies(pkgDir, destNodeModules) {
|
|
|
1153
1159
|
} catch { /* keep walking */ }
|
|
1154
1160
|
}
|
|
1155
1161
|
}
|
|
1156
|
-
} catch {
|
|
1157
|
-
if (!depDir)
|
|
1162
|
+
} catch { /* required dependency failures are handled below */ }
|
|
1163
|
+
if (!depDir) {
|
|
1164
|
+
if (options.strict && !optional) {
|
|
1165
|
+
throw Object.assign(new Error(`Required runtime dependency is unavailable: ${depName}`), { code: 'RUNTIME_DEPENDENCY_MISSING' });
|
|
1166
|
+
}
|
|
1167
|
+
// An optional miss must not hide a later required reference.
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1158
1170
|
}
|
|
1171
|
+
let destination = join(destNodeModules, depName);
|
|
1172
|
+
if (options.strict && resolvedPackages.has(depName) && resolvedPackages.get(depName) !== depDir) {
|
|
1173
|
+
// Two consumers may require different installed versions of the same
|
|
1174
|
+
// dependency. Preserve Node's nearest-ancestor resolution, never flatten
|
|
1175
|
+
// the second version over the first or silently use the root version.
|
|
1176
|
+
destination = join(destinationParent, 'node_modules', depName);
|
|
1177
|
+
for (let parent = destinationParent; parent !== dirname(destNodeModules); parent = dirname(parent)) {
|
|
1178
|
+
const ancestor = join(parent, 'node_modules', depName);
|
|
1179
|
+
if (copiedTargets.has(ancestor)) {
|
|
1180
|
+
if (copiedTargets.get(ancestor) === depDir) destination = ancestor;
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
if (parent === dirname(parent)) break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (options.strict && copiedTargets.has(destination)) {
|
|
1187
|
+
if (copiedTargets.get(destination) !== depDir) {
|
|
1188
|
+
throw Object.assign(new Error(`Conflicting runtime dependency destination: ${depName}`), { code: 'RUNTIME_DEPENDENCY_LAYOUT_CONFLICT' });
|
|
1189
|
+
}
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (!options.strict && visited.has(depName)) continue;
|
|
1193
|
+
visited.add(depName);
|
|
1194
|
+
if (!resolvedPackages.has(depName)) resolvedPackages.set(depName, depDir);
|
|
1195
|
+
copiedTargets.set(destination, depDir);
|
|
1159
1196
|
const depPkgJsonPath = join(depDir, 'package.json');
|
|
1160
|
-
copyDirVerbatim(depDir,
|
|
1197
|
+
copyDirVerbatim(depDir, destination, options);
|
|
1161
1198
|
|
|
1162
1199
|
// Include transitive deps
|
|
1163
1200
|
try {
|
|
1164
1201
|
const depPkg = JSON.parse(readFileSync(depPkgJsonPath, 'utf-8'));
|
|
1165
1202
|
for (const t of Object.keys(depPkg.dependencies || {})) {
|
|
1166
|
-
if (!visited.has(t)) queue.push(t);
|
|
1203
|
+
if (options.strict || !visited.has(t)) queue.push({ name: t, from: depDir, destinationParent: destination, optional: Object.hasOwn(depPkg.optionalDependencies || {}, t) });
|
|
1204
|
+
}
|
|
1205
|
+
for (const t of Object.keys(depPkg.optionalDependencies || {})) {
|
|
1206
|
+
if ((options.strict || !visited.has(t)) && !Object.hasOwn(depPkg.dependencies || {}, t)) queue.push({ name: t, from: depDir, destinationParent: destination, optional: true });
|
|
1167
1207
|
}
|
|
1168
|
-
} catch {
|
|
1208
|
+
} catch (error) { if (options.strict) throw error; }
|
|
1169
1209
|
}
|
|
1170
1210
|
}
|
|
1171
1211
|
|
|
@@ -165,6 +165,13 @@ export function buildPackageArtifactManifest(packageRoot, { createdAt = new Date
|
|
|
165
165
|
if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1) {
|
|
166
166
|
throw new Error(`publish artifact entry must be a single-link regular file: ${relativePath}`);
|
|
167
167
|
}
|
|
168
|
+
// A valid signature over an unstable npm archive is not sufficient: npm
|
|
169
|
+
// renames .gitignore and adds read bits to owner-only files on install.
|
|
170
|
+
// Refuse such inputs at the producer. Never transform the signed identity
|
|
171
|
+
// in the installed reader or silently chmod the publisher's source tree.
|
|
172
|
+
if (relativePath.split('/').includes('.gitignore') || ![0o644, 0o755].includes(mode)) {
|
|
173
|
+
throw new Error(`publish artifact is not install-stable: ${relativePath}; prepare a private source copy with 0644/0755 modes and no bundled .gitignore`);
|
|
174
|
+
}
|
|
168
175
|
files[relativePath] = Object.freeze({
|
|
169
176
|
// npm normalizes file modes in the tarball. Signing the on-disk source
|
|
170
177
|
// mode would reject a clean install when its umask differs, even though
|
|
@@ -13,6 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
import { execSync } from 'child_process';
|
|
15
15
|
|
|
16
|
+
/** Platform eligibility only, not proof of execution containment or full E2E. */
|
|
17
|
+
export function assertInstallerPlatformSupported() {
|
|
18
|
+
if (['linux', 'darwin'].includes(process.platform)) return;
|
|
19
|
+
const message = process.platform === 'win32'
|
|
20
|
+
? 'Native Windows installation is not supported. WSL 2 is the proposed Linux execution route, but it has not been validated for this release. Setup stopped before license activation or dependency installation.'
|
|
21
|
+
: `Installation is not supported on ${process.platform}. Setup stopped before license activation or dependency installation.`;
|
|
22
|
+
throw Object.assign(new Error(message), { code: 'UNSUPPORTED_INSTALLATION_PLATFORM' });
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* @typedef {{ name: string, status: 'pass'|'fail'|'warn', version?: string, message: string }} PreflightCheck
|
|
18
27
|
* @typedef {{ passed: boolean, checks: PreflightCheck[] }} PreflightResult
|
|
@@ -25,6 +34,10 @@ import { execSync } from 'child_process';
|
|
|
25
34
|
* @returns {Promise<PreflightResult>}
|
|
26
35
|
*/
|
|
27
36
|
export async function runPreflightCheck(options = {}) {
|
|
37
|
+
try { assertInstallerPlatformSupported(); }
|
|
38
|
+
catch (error) {
|
|
39
|
+
return { passed: false, checks: [{ name: 'platform', status: 'fail', message: error.message }] };
|
|
40
|
+
}
|
|
28
41
|
const checks = [];
|
|
29
42
|
let mandatoryFailed = false;
|
|
30
43
|
|
|
@@ -29,9 +29,8 @@ import { applyInstalledPathReplacement } from './path-replacement.js';
|
|
|
29
29
|
* @param {string[]} allProviders - All CLI provider names
|
|
30
30
|
* @returns {{ generated: string[], skipped: string[] }}
|
|
31
31
|
*/
|
|
32
|
-
export function generateProviderOverlays(targetDir, frameworkSource, allProviders) {
|
|
32
|
+
export function generateProviderOverlays(targetDir, frameworkSource, allProviders, { frameworkDir = join(targetDir, '.chati.dev') } = {}) {
|
|
33
33
|
const result = { generated: [], skipped: [] };
|
|
34
|
-
const frameworkDir = join(targetDir, '.chati.dev');
|
|
35
34
|
|
|
36
35
|
for (const provider of allProviders) {
|
|
37
36
|
const overlayDir = join(frameworkDir, '.adapted', provider);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as yaml from 'js-yaml';
|
|
2
|
+
import { DELEGATED_EXECUTION_BOUNDARY } from '../utils/execution-role.js';
|
|
2
3
|
|
|
3
4
|
const IDE_PROVIDER_MAP = Object.freeze({
|
|
4
5
|
'claude-code': 'claude',
|
|
@@ -221,12 +222,61 @@ Type \`/chati\` to start or resume Chati.dev.
|
|
|
221
222
|
* The user invokes with $chati or Codex auto-detects based on description.
|
|
222
223
|
* Also reads AGENTS.md for project context (auto-generated by context-file-generator).
|
|
223
224
|
*/
|
|
225
|
+
/** Render only the managed Claude entry command; no project configuration writes. */
|
|
226
|
+
export function generateClaudeRouter(options = {}) {
|
|
227
|
+
const { orchestratorPath } = options;
|
|
228
|
+
return `# /chati — Deterministic Router
|
|
229
|
+
|
|
230
|
+
${DELEGATED_EXECUTION_BOUNDARY}
|
|
231
|
+
|
|
232
|
+
## STEP 0 — Run the deterministic pipeline router (MUST be first)
|
|
233
|
+
|
|
234
|
+
Run via Bash:
|
|
235
|
+
\`\`\`
|
|
236
|
+
node .chati.dev/orchestrator/chati-router.js --harness claude
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
Parse the JSON output. This single command already handled: license validation, integrity check, session state, pipeline routing, and team registration (if needed). You do NOT need to run any of these separately.
|
|
240
|
+
|
|
241
|
+
### If \`ok\` is false:
|
|
242
|
+
|
|
243
|
+
| \`error\` value | What to tell the user |
|
|
244
|
+
|---|---|
|
|
245
|
+
| \`license_invalid\` | Display \`license.reason\` as a friendly message. STOP. |
|
|
246
|
+
| \`license_guard_missing\` | "License enforcement is missing. Reinstall with: npx chati-dev init". STOP. |
|
|
247
|
+
| \`integrity_failed\` | "License enforcement was tampered with. Reinstall with: npx chati-dev init". STOP. |
|
|
248
|
+
| anything else | Display error. STOP. |
|
|
249
|
+
|
|
250
|
+
### If \`ok\` is true:
|
|
251
|
+
|
|
252
|
+
1. Use \`session.language\` for every visible response, preface, progress message, question and error, starting with the first response (pt = Português, en = English, es = Español, fr = Français).
|
|
253
|
+
2. Read the full orchestrator at \`${orchestratorPath || '.chati.dev/orchestrator/chati.md'}\`.
|
|
254
|
+
3. The \`action\` field tells you what to do. Go to the matching "Action:" section in the orchestrator.
|
|
255
|
+
4. **If \`action\` is \`spawn_team\`**: the team is ALREADY registered (Step 1 already ran as code). The \`team\` object contains \`team_id\`, \`task_list_path\`, \`mailbox_path\`, \`members\`. Go directly to "Action: Spawn Team" Step 2 and use these values in the agent prompts. DO NOT run \`orchestrate spawn-team\` again — it already ran.
|
|
256
|
+
5. **If \`action\` is \`setup\`**: this is a new project. Follow "Action: Setup".
|
|
257
|
+
6. For any other action: follow the matching section in the orchestrator.
|
|
258
|
+
|
|
259
|
+
**The \`pipeline\` object contains the full JSON from \`orchestrate next\`.** Use its fields (agent_file, spawn_command, handoff_status, etc.) as documented in the orchestrator.
|
|
260
|
+
|
|
261
|
+
**Pending interaction invariant:** If \`action\` is \`resume_pending_interaction\`,
|
|
262
|
+
do not execute a provider command. Display exactly \`pipeline.question\`, with
|
|
263
|
+
no preface, summary or paraphrase. After the user answers, write
|
|
264
|
+
\`{"response":"<exact user response>"}\` to \`pipeline.response_file\` using the
|
|
265
|
+
native file-write tool, never a shell command. Then execute
|
|
266
|
+
\`pipeline.deferred_spawn_command\` exactly once and unchanged. Never execute a
|
|
267
|
+
command merely because a command-shaped field is present.
|
|
268
|
+
|
|
269
|
+
**User input:** $ARGUMENTS
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
|
|
224
273
|
export function generateCodexSkill(options = {}) {
|
|
225
274
|
const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
|
|
226
275
|
return `---
|
|
227
276
|
name: chati
|
|
228
277
|
description: >
|
|
229
278
|
Use this skill when the user wants to start or continue a Chati.dev session.
|
|
279
|
+
It is not for subprocesses already delegated an agent/task by the parent runtime.
|
|
230
280
|
This activates the orchestrator which routes to the correct agent in the pipeline.
|
|
231
281
|
Invoke when the user mentions chati, /chati, or wants to discover, plan, execute, or release a project.
|
|
232
282
|
Do NOT use for general coding questions unrelated to the Chati.dev pipeline.
|
|
@@ -234,6 +284,8 @@ description: >
|
|
|
234
284
|
|
|
235
285
|
# Chati.dev Orchestrator
|
|
236
286
|
|
|
287
|
+
${DELEGATED_EXECUTION_BOUNDARY}
|
|
288
|
+
|
|
237
289
|
## STEP 0 — Run the deterministic router (MUST be the first action)
|
|
238
290
|
|
|
239
291
|
Use the shell in the current project directory and run:
|
|
@@ -315,6 +367,8 @@ export function generateGeminiRouter(options = {}) {
|
|
|
315
367
|
const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
|
|
316
368
|
return `description = "Activate Chati.dev orchestrator"
|
|
317
369
|
prompt = """
|
|
370
|
+
${DELEGATED_EXECUTION_BOUNDARY}
|
|
371
|
+
|
|
318
372
|
STEP 0: Run the deterministic router before reading mutable state or responding:
|
|
319
373
|
|
|
320
374
|
node .chati.dev/orchestrator/chati-router.js --harness gemini
|
|
@@ -348,6 +402,8 @@ export function generateGrokRouter(options = {}) {
|
|
|
348
402
|
const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
|
|
349
403
|
return `# Chati.dev Orchestrator
|
|
350
404
|
|
|
405
|
+
${DELEGATED_EXECUTION_BOUNDARY}
|
|
406
|
+
|
|
351
407
|
## STEP 0: Run the deterministic router first
|
|
352
408
|
|
|
353
409
|
Before reading mutable session files or responding, run:
|
|
@@ -397,6 +453,8 @@ User input: $ARGUMENTS
|
|
|
397
453
|
export function generateGrokGovernanceRules() {
|
|
398
454
|
return `# Chati.dev Grok Governance
|
|
399
455
|
|
|
456
|
+
${DELEGATED_EXECUTION_BOUNDARY}
|
|
457
|
+
|
|
400
458
|
For every CHATI.DEV entry or continuation, run this boundary before reading
|
|
401
459
|
mutable CHATI state or taking an action:
|
|
402
460
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createPublicKey, randomUUID, verify as cryptoVerify } from 'node:crypto';
|
|
2
2
|
import {
|
|
3
3
|
closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync,
|
|
4
|
-
mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, unlinkSync, writeSync,
|
|
4
|
+
mkdirSync, openSync, readFileSync, readdirSync, readSync, realpathSync, renameSync, unlinkSync, writeSync,
|
|
5
5
|
} from 'node:fs';
|
|
6
6
|
import { basename, dirname, join, parse, relative, resolve, sep } from 'node:path';
|
|
7
7
|
import { isIP } from 'node:net';
|
|
@@ -343,6 +343,36 @@ export function replaceInstallationStorageFile(path, content, source) {
|
|
|
343
343
|
return replaceInstallationArtifact(path, content, source);
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/** Enumerate only the pinned directory; callers still authenticate each entry. */
|
|
347
|
+
export function listSafeInstallationStorageFiles(path) {
|
|
348
|
+
let directory;
|
|
349
|
+
try { directory = openPinnedInstallationDirectory(path); }
|
|
350
|
+
catch (error) { if (error?.code === 'ENOENT') return []; throw error; }
|
|
351
|
+
try {
|
|
352
|
+
assertPinnedInstallationDirectory(directory);
|
|
353
|
+
const names = readdirSync(directory.reference);
|
|
354
|
+
if (names.length > 10000) throw catalogError('INSTALLATION_STORAGE_SIZE_LIMIT', 'managed storage exceeds its bounded entry budget');
|
|
355
|
+
return names.sort();
|
|
356
|
+
} finally { closeSync(directory.descriptor); }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Delete only the exact, already authenticated snapshot selected by the caller. */
|
|
360
|
+
export function removeSafeInstallationStorageFile(path, source) {
|
|
361
|
+
const directory = openPinnedInstallationDirectory(dirname(path));
|
|
362
|
+
try {
|
|
363
|
+
runInstallationStorageSecurityTestHook('beforeRemovalCommit', { path, directory: directory.absolute });
|
|
364
|
+
assertPinnedInstallationDirectory(directory);
|
|
365
|
+
const current = readSafeInstallationEntry(directory, basename(path), { allowMissing: true });
|
|
366
|
+
if (current === null) return false;
|
|
367
|
+
if (!sameSnapshot(current.stat, source.stat) || current.content !== source.content) {
|
|
368
|
+
throw catalogError('INSTALLATION_STORAGE_CONFLICT', 'managed storage changed before removal');
|
|
369
|
+
}
|
|
370
|
+
unlinkSync(installationEntryPath(directory, basename(path)));
|
|
371
|
+
fsyncSync(directory.descriptor);
|
|
372
|
+
return true;
|
|
373
|
+
} finally { closeSync(directory.descriptor); }
|
|
374
|
+
}
|
|
375
|
+
|
|
346
376
|
export function readInstallationArtifactFile(projectDir, { allowMissing = true } = {}) {
|
|
347
377
|
const directory = resolve(projectDir, '.chati/v2');
|
|
348
378
|
return readSafeInstallationFile(join(directory, 'installation.json'), { allowMissing });
|