chati-dev 4.5.4 → 4.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/bin/chati.js +35 -1
- package/framework/agents/plan/tasks.md +1 -1
- package/framework/config.yaml +9 -18
- package/framework/constitution.md +30 -20
- package/framework/context/governance.md +3 -1
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +2 -2
- package/framework/domains/agents/orchestrator.yaml +1 -1
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/global.yaml +2 -2
- package/framework/hooks/model-governance.js +9 -0
- package/framework/manifest.json +38 -38
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati.md +27 -3
- package/framework/schemas/session.schema.json +1 -1
- package/framework/tasks/brownfield-wu-architecture-map.md +1 -1
- package/framework/tasks/brownfield-wu-deep-discovery.md +1 -1
- package/framework/tasks/brownfield-wu-dependency-scan.md +1 -1
- package/framework/tasks/brownfield-wu-migration-plan.md +1 -1
- package/framework/tasks/brownfield-wu-report.md +1 -1
- package/framework/tasks/brownfield-wu-risk-assess.md +1 -1
- package/framework/tasks/greenfield-wu-report.md +1 -1
- package/node_modules/@chati/provider-registry/src/index.js +3 -2
- package/node_modules/@chati/tracking-clickup/src/index.js +22 -0
- package/package.json +1 -1
- package/src/config/gemini-hooks-generator.js +10 -4
- package/src/dashboard/layout.js +6 -4
- package/src/installer/core.js +16 -0
- package/src/installer/templates.js +21 -1
- package/src/installer-v2/clickup-preflight.js +32 -0
- package/src/installer-v2/model-catalog-envelope.json +19 -80
- package/src/installer-v2/model-catalog.json +9 -45
- package/src/installer-v2/model-catalog.sig +1 -1
- package/src/installer-v2/wizard-installation.js +2 -2
- package/src/intelligence/registry-manager.js +9 -3
- package/src/orchestrator/cli.js +36 -21
- package/src/orchestrator/clickup-projection.js +25 -8
- package/src/orchestrator/clickup-runtime.js +89 -1
- package/src/orchestrator/planning-runtime.js +1 -2
- package/src/orchestrator/rail-runtime.js +1 -2
- package/src/orchestrator/runtime-installation-v2.js +10 -1
- package/src/wizard/index.js +16 -20
- package/src/wizard/questions.js +7 -8
package/src/orchestrator/cli.js
CHANGED
|
@@ -583,6 +583,19 @@ export function buildParallelSpawnCommand(agents, projectDir, previousAgent, pro
|
|
|
583
583
|
return parts.join(' ');
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
+
export function buildRoutedParallelSpawnCommands(agents, projectDir, previousAgent, timeout = 900000) {
|
|
587
|
+
return agents.map((agent) => {
|
|
588
|
+
const modelInfo = resolveAgentModel(agent, projectDir);
|
|
589
|
+
return Object.freeze({
|
|
590
|
+
agent,
|
|
591
|
+
provider: modelInfo.provider,
|
|
592
|
+
model: modelInfo.model,
|
|
593
|
+
reasoning_configuration: modelInfo.reasoning_configuration,
|
|
594
|
+
command: buildSpawnCommand(agent, projectDir, previousAgent, modelInfo.provider, timeout, modelInfo.model, modelInfo.source === 'installation-v2'),
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
586
599
|
/**
|
|
587
600
|
* Model map — canonical model tier per agent.
|
|
588
601
|
* Matches framework/hooks/model-governance.js AGENT_MODELS.
|
|
@@ -748,7 +761,7 @@ async function handleNext(projectDir) {
|
|
|
748
761
|
// Centralized session lock update: applies to ALL return paths.
|
|
749
762
|
// Activates lock when an agent is returned, deactivates on completion.
|
|
750
763
|
if (result && result.agent && result.action !== 'error' && result.action !== 'setup') {
|
|
751
|
-
const activating = ['activate_interactive', 'spawn_autonomous', 'spawn_team', 'spawn_parallel'];
|
|
764
|
+
const activating = ['activate_interactive', 'spawn_routed_interactive', 'spawn_autonomous', 'spawn_team', 'spawn_parallel'];
|
|
752
765
|
if (activating.includes(result.action)) {
|
|
753
766
|
try {
|
|
754
767
|
writeSessionLock(projectDir, result.agent, {
|
|
@@ -830,20 +843,18 @@ async function _handleNextInner(projectDir) {
|
|
|
830
843
|
};
|
|
831
844
|
}
|
|
832
845
|
|
|
846
|
+
const modelInfo = resolveAgentModel(agent, projectDir);
|
|
833
847
|
return {
|
|
834
|
-
action: isInteractive ? '
|
|
848
|
+
action: isInteractive ? 'spawn_routed_interactive' : 'spawn_autonomous',
|
|
835
849
|
agent,
|
|
836
850
|
agent_file: agentFile,
|
|
837
851
|
phase: agentDef?.phase || session.mode,
|
|
838
|
-
spawn_command:
|
|
839
|
-
const modelInfo = resolveAgentModel(agent, projectDir);
|
|
840
|
-
return buildSpawnCommand(agent, projectDir, session.last_handoff || 'none', modelInfo.provider, 600000, modelInfo.model, modelInfo.source === 'installation-v2');
|
|
841
|
-
})(),
|
|
852
|
+
spawn_command: buildSpawnCommand(agent, projectDir, session.last_handoff || 'none', modelInfo.provider, 600000, modelInfo.model, modelInfo.source === 'installation-v2'),
|
|
842
853
|
parallel_spawn_command: null,
|
|
843
854
|
parallel_agents: [],
|
|
844
855
|
handoff_status: { valid: true, missing: [], warnings: ['Resuming in-progress agent'] },
|
|
845
856
|
gate_status: { canAdvance: true, reason: 'Agent already in progress' },
|
|
846
|
-
model_info:
|
|
857
|
+
model_info: modelInfo,
|
|
847
858
|
context_bracket: estimateContextBracket(completedAgents.length, AGENT_PIPELINE.length),
|
|
848
859
|
pipeline_progress: getPipelineProgress(pipelineState),
|
|
849
860
|
session: { language: session.language, project_type: session.project_type || session.project?.type, execution_mode: session.execution_mode, user_level: session.user_level || 'auto' },
|
|
@@ -861,17 +872,18 @@ async function _handleNextInner(projectDir) {
|
|
|
861
872
|
const firstAgent = projectType === 'brownfield' ? 'brownfield-wu' : 'greenfield-wu';
|
|
862
873
|
const agentFile = getAgentFile(firstAgent, projectDir) || null;
|
|
863
874
|
|
|
875
|
+
const modelInfo = resolveAgentModel(firstAgent, projectDir);
|
|
864
876
|
return {
|
|
865
|
-
action: '
|
|
877
|
+
action: 'spawn_routed_interactive',
|
|
866
878
|
agent: firstAgent,
|
|
867
879
|
agent_file: agentFile,
|
|
868
880
|
phase: 'discover',
|
|
869
|
-
spawn_command:
|
|
881
|
+
spawn_command: buildSpawnCommand(firstAgent, projectDir, 'none', modelInfo.provider, 600000, modelInfo.model, modelInfo.source === 'installation-v2'),
|
|
870
882
|
parallel_spawn_command: null,
|
|
871
883
|
parallel_agents: [],
|
|
872
884
|
handoff_status: { valid: true, missing: [], warnings: [] },
|
|
873
885
|
gate_status: { canAdvance: true, reason: 'First agent in pipeline' },
|
|
874
|
-
model_info:
|
|
886
|
+
model_info: modelInfo,
|
|
875
887
|
context_bracket: estimateContextBracket(0, AGENT_PIPELINE.length),
|
|
876
888
|
pipeline_progress: getPipelineProgress(pipelineState),
|
|
877
889
|
session: { language: session.language, project_type: projectType, execution_mode: session.execution_mode, user_level: session.user_level || 'auto' },
|
|
@@ -932,7 +944,7 @@ async function _handleNextInner(projectDir) {
|
|
|
932
944
|
const modelInfo = resolveAgentModel(nextAgent, projectDir);
|
|
933
945
|
|
|
934
946
|
// Check for parallel group — with Agent Teams override (Article XXI)
|
|
935
|
-
let action, spawnCommand = null, parallelSpawnCommand = null, parallelAgents = [];
|
|
947
|
+
let action, spawnCommand = null, parallelSpawnCommand = null, parallelSpawnCommands = [], parallelAgents = [];
|
|
936
948
|
let teamData = null;
|
|
937
949
|
const teamsEnabled = isAgentTeamsEnabled(projectDir);
|
|
938
950
|
|
|
@@ -948,7 +960,11 @@ async function _handleNextInner(projectDir) {
|
|
|
948
960
|
} else {
|
|
949
961
|
action = 'spawn_parallel';
|
|
950
962
|
parallelAgents = nextInfo.group;
|
|
951
|
-
|
|
963
|
+
if (modelInfo.source === 'installation-v2') {
|
|
964
|
+
parallelSpawnCommands = buildRoutedParallelSpawnCommands(nextInfo.group, projectDir, lastAgent);
|
|
965
|
+
} else {
|
|
966
|
+
parallelSpawnCommand = buildParallelSpawnCommand(nextInfo.group, projectDir, lastAgent, modelInfo.provider, 900000, modelInfo.model, false);
|
|
967
|
+
}
|
|
952
968
|
}
|
|
953
969
|
} else if (nextAgent === 'dev' && teamsEnabled) {
|
|
954
970
|
// Build Team auto-spawn: dev + qa-implementation are NOT parallel in the
|
|
@@ -962,7 +978,8 @@ async function _handleNextInner(projectDir) {
|
|
|
962
978
|
parallelAgents = teamConfig.members;
|
|
963
979
|
teamData = { team_id: teamId, team_type: 'build', members: teamConfig.members };
|
|
964
980
|
} else if (isInteractive) {
|
|
965
|
-
action = '
|
|
981
|
+
action = 'spawn_routed_interactive';
|
|
982
|
+
spawnCommand = buildSpawnCommand(nextAgent, projectDir, lastAgent, modelInfo.provider, 600000, modelInfo.model, modelInfo.source === 'installation-v2');
|
|
966
983
|
} else {
|
|
967
984
|
action = 'spawn_autonomous';
|
|
968
985
|
spawnCommand = buildSpawnCommand(nextAgent, projectDir, lastAgent, modelInfo.provider, 600000, modelInfo.model, modelInfo.source === 'installation-v2');
|
|
@@ -1000,6 +1017,7 @@ async function _handleNextInner(projectDir) {
|
|
|
1000
1017
|
phase: agentDef?.phase || session.mode,
|
|
1001
1018
|
spawn_command: spawnCommand,
|
|
1002
1019
|
parallel_spawn_command: parallelSpawnCommand,
|
|
1020
|
+
parallel_spawn_commands: parallelSpawnCommands,
|
|
1003
1021
|
parallel_agents: parallelAgents,
|
|
1004
1022
|
handoff_status: handoffStatus,
|
|
1005
1023
|
gate_status: gateStatus,
|
|
@@ -2418,16 +2436,13 @@ async function handleProviders(projectDir) {
|
|
|
2418
2436
|
agentModels[agentDef.name] = resolveAgentModel(agentDef.name, projectDir);
|
|
2419
2437
|
}
|
|
2420
2438
|
|
|
2421
|
-
|
|
2422
|
-
const
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
const providerMatch = raw.match(/primary_provider:\s*["']?(\w+)/);
|
|
2426
|
-
if (providerMatch) primaryProvider = providerMatch[1].toLowerCase();
|
|
2427
|
-
}
|
|
2439
|
+
const artifact = loadRuntimeInstallationV2(projectDir);
|
|
2440
|
+
const enabledProviders = artifact
|
|
2441
|
+
? artifact.installation.enabled_providers.map(({ provider_id, harness_id }) => ({ provider_id, harness_id }))
|
|
2442
|
+
: [...new Set(Object.values(agentModels).map(({ provider }) => provider).filter(Boolean))].map((harness_id) => ({ harness_id }));
|
|
2428
2443
|
|
|
2429
2444
|
return {
|
|
2430
|
-
|
|
2445
|
+
enabled_providers: enabledProviders,
|
|
2431
2446
|
agent_models: agentModels,
|
|
2432
2447
|
};
|
|
2433
2448
|
}
|
|
@@ -61,14 +61,8 @@ export function enqueueClickUpProjectionFromState({ projectDir, handoff, records
|
|
|
61
61
|
...(completion?.reviews || []),
|
|
62
62
|
...(completion?.acceptance_evidence_refs || []),
|
|
63
63
|
]);
|
|
64
|
-
const tracking =
|
|
65
|
-
|
|
66
|
-
project_evolution_dir: join(projectDir, '.chati/v2/project-evolution'),
|
|
67
|
-
clock, verify_completion: (candidate) => completionIsCanonical(records, candidate),
|
|
68
|
-
verify_reference: (ref) => refs.has(ref),
|
|
69
|
-
resolve_reference: (ref, type) => type === 'handoff'
|
|
70
|
-
? { phase: 'rail-execution', handoff_id: handoff.handoff_id }
|
|
71
|
-
: { task_state: taskState(records, handoff.handoff_id, task_id), blockers: journalBlockers(records, handoff.handoff_id, task_id) },
|
|
64
|
+
const tracking = createClickUpTrackingFromState({
|
|
65
|
+
projectDir, handoff, records, task_id, clock, extra_verified_refs: [...refs],
|
|
72
66
|
});
|
|
73
67
|
const projection = tracking.enqueue({
|
|
74
68
|
projection_id: `clickup-${sha256({ handoff_id: handoff.handoff_id, task_id, attempt_id, operation, payload }).slice(0, 24)}`,
|
|
@@ -82,3 +76,26 @@ export function enqueueClickUpProjectionFromState({ projectDir, handoff, records
|
|
|
82
76
|
}
|
|
83
77
|
return projection;
|
|
84
78
|
}
|
|
79
|
+
|
|
80
|
+
export function createClickUpTrackingFromState({ projectDir, handoff, records, task_id, clock = () => new Date(), extra_verified_refs = [] } = {}) {
|
|
81
|
+
const task = handoff.tasks.find((item) => item.task_id === task_id);
|
|
82
|
+
if (!task) throw Object.assign(new Error(`task ${task_id} is absent from handoff`), { code: 'TASK_NOT_FOUND' });
|
|
83
|
+
const handoff_ref = `rail-handoff://${handoff.handoff_id}/${handoff.seal.manifest_digest}`;
|
|
84
|
+
const journal_ref = `rail-journal://sha256/${sha256(records)}`;
|
|
85
|
+
const derivedDecisions = journalDecisions(records, handoff.handoff_id, task_id);
|
|
86
|
+
const refs = new Set([
|
|
87
|
+
handoff_ref,
|
|
88
|
+
journal_ref,
|
|
89
|
+
...derivedDecisions.flatMap((item) => [item.decision_ref, item.review_ref]),
|
|
90
|
+
...extra_verified_refs,
|
|
91
|
+
]);
|
|
92
|
+
return new TrackingClickUp({
|
|
93
|
+
outbox_path: join(projectDir, '.chati/v2/tracking/clickup-outbox.json'),
|
|
94
|
+
project_evolution_dir: join(projectDir, '.chati/v2/project-evolution'),
|
|
95
|
+
clock, verify_completion: (candidate) => completionIsCanonical(records, candidate),
|
|
96
|
+
verify_reference: (ref) => refs.has(ref),
|
|
97
|
+
resolve_reference: (ref, type) => type === 'handoff'
|
|
98
|
+
? { phase: 'rail-execution', handoff_id: handoff.handoff_id }
|
|
99
|
+
: { task_state: taskState(records, handoff.handoff_id, task_id), blockers: journalBlockers(records, handoff.handoff_id, task_id) },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { sha256 } from '@chati/core';
|
|
1
4
|
import { createProjectRailEngine, loadRailHandoff } from './rail-runtime.js';
|
|
2
|
-
import { enqueueClickUpProjectionFromState } from './clickup-projection.js';
|
|
5
|
+
import { createClickUpTrackingFromState, enqueueClickUpProjectionFromState } from './clickup-projection.js';
|
|
3
6
|
|
|
4
7
|
/** Enqueues a durable local ClickUp projection. It never sends to ClickUp. */
|
|
5
8
|
export function enqueueClickUpProjection({ projectDir, handoff_id, task_id, attempt_id, operation = 'update', payload, completion, decisions, clock = () => new Date() } = {}) {
|
|
@@ -11,3 +14,88 @@ export function enqueueClickUpProjection({ projectDir, handoff_id, task_id, atte
|
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export { enqueueClickUpProjectionFromState } from './clickup-projection.js';
|
|
17
|
+
|
|
18
|
+
export function listPendingClickUpProjections({ projectDir } = {}) {
|
|
19
|
+
const path = join(projectDir, '.chati/v2/tracking/clickup-outbox.json');
|
|
20
|
+
if (!existsSync(path)) return Object.freeze([]);
|
|
21
|
+
const outbox = JSON.parse(readFileSync(path, 'utf8'));
|
|
22
|
+
return Object.freeze((outbox.projections || [])
|
|
23
|
+
.filter((projection) => ['pending', 'retryable_failure', 'sent_unconfirmed'].includes(projection.delivery_state))
|
|
24
|
+
.map((projection) => Object.freeze({
|
|
25
|
+
projection_id: projection.projection_id,
|
|
26
|
+
clickup_ref: projection.clickup_ref,
|
|
27
|
+
operation: projection.operation,
|
|
28
|
+
payload: projection.payload,
|
|
29
|
+
idempotency_key: projection.idempotency_key,
|
|
30
|
+
})));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SENSITIVE_RECEIPT_KEY = /(authorization|cookie|credential|password|secret|token)/i;
|
|
34
|
+
|
|
35
|
+
export function sanitizeClickUpReceipt(value, depth = 0) {
|
|
36
|
+
if (depth > 8) return '[truncated]';
|
|
37
|
+
if (Array.isArray(value)) return value.slice(0, 100).map((item) => sanitizeClickUpReceipt(item, depth + 1));
|
|
38
|
+
if (!value || typeof value !== 'object') return value;
|
|
39
|
+
return Object.fromEntries(Object.entries(value)
|
|
40
|
+
.filter(([key]) => !SENSITIVE_RECEIPT_KEY.test(key))
|
|
41
|
+
.map(([key, item]) => [key, sanitizeClickUpReceipt(item, depth + 1)]));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasRemoteReceiptIdentifier(value, depth = 0) {
|
|
45
|
+
if (depth > 8 || !value || typeof value !== 'object') return false;
|
|
46
|
+
if (Array.isArray(value)) return value.some((item) => hasRemoteReceiptIdentifier(item, depth + 1));
|
|
47
|
+
return Object.entries(value).some(([key, item]) =>
|
|
48
|
+
(/^(id|task_id|comment_id|task_url|url)$/i.test(key) && typeof item === 'string' && item.trim() !== '')
|
|
49
|
+
|| hasRemoteReceiptIdentifier(item, depth + 1));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function clickUpReceiptConfirmsSuccess(receipt) {
|
|
53
|
+
if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) return false;
|
|
54
|
+
if (receipt.isError === true || receipt.success === false) return false;
|
|
55
|
+
if (typeof receipt.status === 'string' && /^(failed|error|unauthorized)$/i.test(receipt.status)) return false;
|
|
56
|
+
return receipt.success === true
|
|
57
|
+
|| (typeof receipt.status === 'string' && /^(success|ok|completed)$/i.test(receipt.status))
|
|
58
|
+
|| hasRemoteReceiptIdentifier(receipt);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function acknowledgeClickUpProjection({ projectDir, handoff_id, projection_id, receipt, clock = () => new Date() } = {}) {
|
|
62
|
+
if (!receipt || typeof receipt !== 'object' || Array.isArray(receipt)) throw Object.assign(new Error('receipt must be an object'), { code: 'INVALID_CLICKUP_RECEIPT' });
|
|
63
|
+
const { handoff } = loadRailHandoff({ projectDir, handoff_id });
|
|
64
|
+
const outboxPath = join(projectDir, '.chati/v2/tracking/clickup-outbox.json');
|
|
65
|
+
const outbox = JSON.parse(readFileSync(outboxPath, 'utf8'));
|
|
66
|
+
const projection = outbox.projections?.find((item) => item.projection_id === projection_id);
|
|
67
|
+
if (!projection) throw Object.assign(new Error(`projection ${projection_id} does not exist`), { code: 'PROJECTION_NOT_FOUND' });
|
|
68
|
+
const records = createProjectRailEngine({ projectDir, clock }).records;
|
|
69
|
+
if (projection.delivery_state === 'confirmed' && projection.receipt_ref) {
|
|
70
|
+
const tracking = createClickUpTrackingFromState({
|
|
71
|
+
projectDir, handoff, records, task_id: projection.task_id, clock, extra_verified_refs: [projection.receipt_ref],
|
|
72
|
+
});
|
|
73
|
+
return tracking.confirmExternalDelivery({ projection_id, receipt_ref: projection.receipt_ref });
|
|
74
|
+
}
|
|
75
|
+
if (!clickUpReceiptConfirmsSuccess(receipt)) {
|
|
76
|
+
throw Object.assign(new Error('receipt does not prove a successful ClickUp operation'), { code: 'CLICKUP_RECEIPT_UNCONFIRMED' });
|
|
77
|
+
}
|
|
78
|
+
const observed = clock();
|
|
79
|
+
const receivedAt = (observed instanceof Date ? observed : new Date(observed)).toISOString();
|
|
80
|
+
const safeReceipt = sanitizeClickUpReceipt(receipt);
|
|
81
|
+
const receiptMaterial = { schema_version: 1, projection_id, received_at: receivedAt, response: safeReceipt };
|
|
82
|
+
if (JSON.stringify(receiptMaterial).length > 64 * 1024) {
|
|
83
|
+
throw Object.assign(new Error('receipt exceeds 64 KiB after sanitization'), { code: 'CLICKUP_RECEIPT_TOO_LARGE' });
|
|
84
|
+
}
|
|
85
|
+
const digest = sha256(receiptMaterial);
|
|
86
|
+
const receiptPath = join(projectDir, '.chati/v2/tracking/receipts', `${digest}.json`);
|
|
87
|
+
mkdirSync(dirname(receiptPath), { recursive: true, mode: 0o700 });
|
|
88
|
+
const serialized = `${JSON.stringify(receiptMaterial, null, 2)}\n`;
|
|
89
|
+
if (!existsSync(receiptPath)) {
|
|
90
|
+
const temporary = `${receiptPath}.${process.pid}.tmp`;
|
|
91
|
+
writeFileSync(temporary, serialized, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
92
|
+
renameSync(temporary, receiptPath);
|
|
93
|
+
} else if (readFileSync(receiptPath, 'utf8') !== serialized) {
|
|
94
|
+
throw Object.assign(new Error('receipt digest collision'), { code: 'CLICKUP_RECEIPT_CONFLICT' });
|
|
95
|
+
}
|
|
96
|
+
const receiptRef = `clickup-receipt://sha256/${digest}`;
|
|
97
|
+
const tracking = createClickUpTrackingFromState({
|
|
98
|
+
projectDir, handoff, records, task_id: projection.task_id, clock, extra_verified_refs: [receiptRef],
|
|
99
|
+
});
|
|
100
|
+
return tracking.confirmExternalDelivery({ projection_id, receipt_ref: receiptRef });
|
|
101
|
+
}
|
|
@@ -87,8 +87,7 @@ export function compilePlanningHandoff({ projectDir, clock = () => new Date() }
|
|
|
87
87
|
};
|
|
88
88
|
if (sources.ux_path) source_artifacts.ux_ref = immutableFileRef(projectDir, sources.ux_path, 'PLANNING_SOURCE_ARTIFACTS_REQUIRED');
|
|
89
89
|
const ids = new Set();
|
|
90
|
-
const clickupRequired = installation.installation.
|
|
91
|
-
&& installation.installation.external_integrations?.clickup === 'required';
|
|
90
|
+
const clickupRequired = installation.installation.external_integrations?.clickup === 'required';
|
|
92
91
|
const tasks = planning.tasks.map((task) => {
|
|
93
92
|
if (!task || typeof task !== 'object' || typeof task.id !== 'string' || !task.id.trim() || ids.has(task.id)) fail('INVALID_PLANNING_TASK', 'each planning task needs a unique id');
|
|
94
93
|
ids.add(task.id);
|
|
@@ -85,8 +85,7 @@ function loadProjectRail({ projectDir, handoff_id, clock }) {
|
|
|
85
85
|
|
|
86
86
|
function internalTrackingInstallation(projectDir, clock) {
|
|
87
87
|
const artifact = loadRuntimeInstallationV2(projectDir, { ...(clock === undefined ? {} : { clock }) });
|
|
88
|
-
return artifact?.installation?.
|
|
89
|
-
&& artifact.installation.external_integrations?.clickup === 'required';
|
|
88
|
+
return artifact?.installation?.external_integrations?.clickup === 'required';
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
function assertCompletionMetricsEvidence(metrics, reviews, acceptanceEvidenceRefs, records, attemptId) {
|
|
@@ -48,9 +48,18 @@ export function resolveRuntimeInvocationV2({ artifact, agent, binding, model_id,
|
|
|
48
48
|
if (!selectedCandidate) throw Object.assign(new Error(`No eligible installed model for ${agent}/${action}`), { code: 'NO_ELIGIBLE_MODEL' });
|
|
49
49
|
const selected = selectedCandidate.binding;
|
|
50
50
|
const model = selectedCandidate.model.model_id;
|
|
51
|
+
const defaultReasoning = selectedCandidate.model.tier === 'worker'
|
|
52
|
+
? 'low'
|
|
53
|
+
: selectedCandidate.model.tier === 'workhorse'
|
|
54
|
+
? 'medium'
|
|
55
|
+
: 'high';
|
|
51
56
|
const invocation = {
|
|
52
57
|
provider_id: selected.provider_id, harness_id: selected.harness_id, action,
|
|
53
|
-
model_pin: {
|
|
58
|
+
model_pin: {
|
|
59
|
+
model_id: model,
|
|
60
|
+
catalog_snapshot_ref: artifact.capability_snapshot.snapshot_id,
|
|
61
|
+
reasoning_configuration: reasoning_configuration ?? defaultReasoning,
|
|
62
|
+
},
|
|
54
63
|
};
|
|
55
64
|
assertEligibleInvocation({ installation: artifact.installation, snapshot: artifact.capability_snapshot, invocation, clock });
|
|
56
65
|
return Object.freeze(invocation);
|
package/src/wizard/index.js
CHANGED
|
@@ -3,12 +3,13 @@ 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 { WIZARD_BACK, stepLanguage, stepProjectType, stepProviderSelection,
|
|
6
|
+
import { WIZARD_BACK, stepLanguage, stepProjectType, stepProviderSelection, stepInstallationMode, stepConfirmation, stepTermsOfUse } from './questions.js';
|
|
7
7
|
import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
|
|
8
8
|
import { installFramework } from '../installer/core.js';
|
|
9
9
|
import { INSTALLATION_ARTIFACT_PATH, dryRunV2, installV2, reconfigureV2 } from '../installer-v2/index.js';
|
|
10
10
|
import { buildWizardV2InstallationInput } from '../installer-v2/wizard-installation.js';
|
|
11
11
|
import { resolveCapabilityCatalog, verifySignedCapabilityCatalog } from '../installer-v2/catalog-client.js';
|
|
12
|
+
import { checkClickUpMcp } from '../installer-v2/clickup-preflight.js';
|
|
12
13
|
import { validateInstallation } from '../installer/validator.js';
|
|
13
14
|
import { initCollector, track as telemetryTrack, flush as telemetryFlush } from '../telemetry/collector.js';
|
|
14
15
|
import { sendEvents } from '../telemetry/sender.js';
|
|
@@ -72,18 +73,20 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
72
73
|
let catalogResolution;
|
|
73
74
|
let modelSelections;
|
|
74
75
|
let installationMode;
|
|
75
|
-
let
|
|
76
|
+
let clickupPreflight = null;
|
|
77
|
+
// Editor rule files are a legacy programmatic integration. The interactive
|
|
78
|
+
// installer configures provider CLIs only, which are the actual runtimes.
|
|
79
|
+
const selectedEditors = options.editors || [];
|
|
76
80
|
let config;
|
|
77
81
|
let v2InstallationInput;
|
|
78
82
|
let stage = 'project';
|
|
79
83
|
|
|
80
|
-
const stageOrder = ['language', 'project', 'providers', 'mode', '
|
|
84
|
+
const stageOrder = ['language', 'project', 'providers', 'mode', 'confirm'];
|
|
81
85
|
const optionKey = {
|
|
82
86
|
language: 'language',
|
|
83
87
|
project: 'projectType',
|
|
84
88
|
providers: 'providers',
|
|
85
89
|
mode: 'installationMode',
|
|
86
|
-
editors: 'editors',
|
|
87
90
|
};
|
|
88
91
|
const previousInteractiveStage = (current) => {
|
|
89
92
|
for (let index = stageOrder.indexOf(current) - 1; index >= 0; index -= 1) {
|
|
@@ -127,9 +130,9 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
127
130
|
catalogResolution ??= options.signedCapabilityCatalogEnvelope
|
|
128
131
|
? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
|
|
129
132
|
: await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
// Provider selection defines the permitted routing pool. Model discovery
|
|
134
|
+
// and task-specific choice stay inside the control plane.
|
|
135
|
+
modelSelections = options.modelSelections;
|
|
133
136
|
stage = 'mode';
|
|
134
137
|
continue;
|
|
135
138
|
}
|
|
@@ -151,20 +154,12 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
151
154
|
p.log.error(error.message);
|
|
152
155
|
continue;
|
|
153
156
|
}
|
|
157
|
+
clickupPreflight = options.clickupPreflight || checkClickUpMcp(selectedProviders);
|
|
158
|
+
if (!clickupPreflight.passed) {
|
|
159
|
+
p.log.error('Internal mode requires an authorized ClickUp MCP connection in at least one selected CLI. Configure ClickUp in Claude, Codex or Grok, then retry.');
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
154
162
|
}
|
|
155
|
-
stage = 'editors';
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (stage === 'editors') {
|
|
160
|
-
const value = options.editors !== undefined
|
|
161
|
-
? options.editors
|
|
162
|
-
: await stepEditorSelection({ allowBack: previousInteractiveStage('editors') !== null });
|
|
163
|
-
if (value === WIZARD_BACK) {
|
|
164
|
-
stage = previousInteractiveStage('editors');
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
selectedEditors = value;
|
|
168
163
|
stage = 'confirm';
|
|
169
164
|
continue;
|
|
170
165
|
}
|
|
@@ -185,6 +180,7 @@ export async function runWizard(targetDir, options = {}) {
|
|
|
185
180
|
selectedIDEs,
|
|
186
181
|
selectedMCPs,
|
|
187
182
|
installationMode,
|
|
183
|
+
clickupPreflight,
|
|
188
184
|
modelSelections,
|
|
189
185
|
targetDir,
|
|
190
186
|
version: VERSION,
|
package/src/wizard/questions.js
CHANGED
|
@@ -120,8 +120,8 @@ export async function stepProviderSelection({ allowBack = false } = {}) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
|
-
* Selects the operational policy profile.
|
|
124
|
-
* ClickUp tracking;
|
|
123
|
+
* Selects the operational policy profile. Authorized internal projects require
|
|
124
|
+
* ClickUp tracking; open projects do not gain that external dependency.
|
|
125
125
|
*/
|
|
126
126
|
export async function stepInstallationMode({ allowBack = false } = {}) {
|
|
127
127
|
const mode = await p.select({
|
|
@@ -244,16 +244,12 @@ export async function stepIDESelection() {
|
|
|
244
244
|
*/
|
|
245
245
|
export async function stepModelSelection(providers, capabilityCatalog) {
|
|
246
246
|
const selections = {};
|
|
247
|
-
const summary = [];
|
|
248
247
|
|
|
249
248
|
for (const provider of providers) {
|
|
250
249
|
const models = wizardModelSuggestions(provider, capabilityCatalog);
|
|
251
250
|
selections[provider] = [...models];
|
|
252
|
-
summary.push(`${PROVIDER_DISPLAY_NAMES[provider] || provider}: ${models.join(', ')}`);
|
|
253
251
|
}
|
|
254
252
|
|
|
255
|
-
p.note(summary.join('\n'), t('installer.model_routing_label'));
|
|
256
|
-
|
|
257
253
|
return selections;
|
|
258
254
|
}
|
|
259
255
|
|
|
@@ -261,7 +257,7 @@ export async function stepModelSelection(providers, capabilityCatalog) {
|
|
|
261
257
|
* Step 4: Confirmation
|
|
262
258
|
*/
|
|
263
259
|
export async function stepConfirmation(config, { allowBack = false } = {}) {
|
|
264
|
-
const { projectName, projectType, language, selectedMCPs, selectedIDEs, allProviders, installationMode } = config;
|
|
260
|
+
const { projectName, projectType, language, selectedMCPs, selectedIDEs, allProviders, installationMode, clickupPreflight } = config;
|
|
265
261
|
|
|
266
262
|
const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
|
|
267
263
|
|
|
@@ -282,7 +278,6 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
|
|
|
282
278
|
[t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
|
|
283
279
|
[t('installer.language_label')]: langName,
|
|
284
280
|
[t('installer.providers_label')]: providersDisplay,
|
|
285
|
-
[t('installer.model_routing_label')]: t('installer.model_routing_automatic'),
|
|
286
281
|
[t('installer.installation_mode_label')]: installationMode === 'internal'
|
|
287
282
|
? t('installer.installation_mode_internal')
|
|
288
283
|
: t('installer.installation_mode_open'),
|
|
@@ -293,6 +288,10 @@ export async function stepConfirmation(config, { allowBack = false } = {}) {
|
|
|
293
288
|
summaryData[t('installer.ides_label')] = editorNames;
|
|
294
289
|
}
|
|
295
290
|
|
|
291
|
+
if (installationMode === 'internal') {
|
|
292
|
+
summaryData.ClickUp = `Connected via ${(clickupPreflight?.connected_providers || []).join(', ')}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
296
295
|
summaryData[t('installer.mcps_label')] = mcpNames;
|
|
297
296
|
|
|
298
297
|
console.log();
|