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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { canonicalSerialize } from '@chati/core';
|
|
4
|
+
import { loadRuntimeInstallationV2 } from './runtime-installation-v2.js';
|
|
5
|
+
import { verifyRailEvidenceAuthorityCertificate } from './rail-evidence-authority.js';
|
|
6
|
+
import { verifyClickUpMcpAuthorityCertificate } from './clickup-runtime.js';
|
|
7
|
+
import {
|
|
8
|
+
persistImmutableInstallationFile, readSafeInstallationStorageFile, replaceInstallationStorageFile,
|
|
9
|
+
} from '../installer-v2/catalog-client.js';
|
|
10
|
+
|
|
11
|
+
const MAX_CERTIFICATE_BYTES = 32 * 1024;
|
|
12
|
+
const CERTIFICATE_TYPES = Object.freeze({
|
|
13
|
+
'chati-rail-evidence-authority': Object.freeze({
|
|
14
|
+
path: '.chati/v2/rail/evidence-authority.json', verify: verifyRailEvidenceAuthorityCertificate,
|
|
15
|
+
}),
|
|
16
|
+
'chati-clickup-mcp-authority': Object.freeze({
|
|
17
|
+
path: '.chati/v2/tracking/clickup-mcp-authority.json', verify: verifyClickUpMcpAuthorityCertificate,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function fail(code, message) { throw Object.assign(new Error(message), { code }); }
|
|
22
|
+
|
|
23
|
+
/** Public-certificate enrollment only. Never generates or trusts an issuer key. */
|
|
24
|
+
export function enrollProjectEvidenceAuthority({ projectDir, certificate, replace = false, clock = () => new Date() } = {}) {
|
|
25
|
+
const artifact = loadRuntimeInstallationV2(projectDir, { clock });
|
|
26
|
+
if (!artifact) fail('V2_INSTALLATION_REQUIRED', 'evidence authority enrollment requires a verified v2 installation');
|
|
27
|
+
if (typeof replace !== 'boolean') fail('INVALID_AUTHORITY_ENROLLMENT', 'replace must be an explicit boolean');
|
|
28
|
+
const definition = Object.hasOwn(CERTIFICATE_TYPES, certificate?.certificate_type ?? '')
|
|
29
|
+
? CERTIFICATE_TYPES[certificate.certificate_type] : null;
|
|
30
|
+
if (!definition) fail('INVALID_AUTHORITY_CERTIFICATE_TYPE', 'expected a CHATI RAIL evidence or ClickUp MCP authority certificate');
|
|
31
|
+
const rootPublicKey = readFileSync(new URL('../installer/signing-public-key.pem', import.meta.url), 'utf8');
|
|
32
|
+
// Verify the original closed-schema object before serializing or mutating storage.
|
|
33
|
+
definition.verify({ certificate, expected_project_id: artifact.project_id, root_public_key: rootPublicKey, clock });
|
|
34
|
+
// createPublicKey also accepts private PEM and ignores trailing PEM blocks.
|
|
35
|
+
// Do not persist such input or normalize a signed certificate into new bytes.
|
|
36
|
+
if (typeof certificate.public_key !== 'string'
|
|
37
|
+
|| !/^-----BEGIN PUBLIC KEY-----\r?\n(?:[A-Za-z0-9+/=]+\r?\n)+-----END PUBLIC KEY-----$/.test(certificate.public_key.trim())) {
|
|
38
|
+
fail('AUTHORITY_PUBLIC_KEY_REQUIRED', 'certificate public_key must contain exactly one public SPKI PEM block');
|
|
39
|
+
}
|
|
40
|
+
const content = `${canonicalSerialize(certificate)}\n`;
|
|
41
|
+
if (Buffer.byteLength(content) > MAX_CERTIFICATE_BYTES) fail('AUTHORITY_CERTIFICATE_SIZE_LIMIT', 'authority certificate exceeds 32 KiB');
|
|
42
|
+
const path = join(projectDir, definition.path);
|
|
43
|
+
const previous = readSafeInstallationStorageFile(path, {
|
|
44
|
+
allowMissing: true, label: 'evidence authority certificate', maxBytes: MAX_CERTIFICATE_BYTES,
|
|
45
|
+
unsafeCode: 'UNSAFE_AUTHORITY_CERTIFICATE_STORAGE', sizeCode: 'AUTHORITY_CERTIFICATE_SIZE_LIMIT',
|
|
46
|
+
});
|
|
47
|
+
let written = false;
|
|
48
|
+
if (previous === null) {
|
|
49
|
+
written = persistImmutableInstallationFile(path, content, {
|
|
50
|
+
label: 'evidence authority certificate', maxBytes: MAX_CERTIFICATE_BYTES,
|
|
51
|
+
conflictCode: 'AUTHORITY_CERTIFICATE_CONFLICT',
|
|
52
|
+
});
|
|
53
|
+
} else if (previous.content !== content) {
|
|
54
|
+
if (!replace) fail('AUTHORITY_CERTIFICATE_CONFLICT', 'another certificate is enrolled; use --replace explicitly to rotate it');
|
|
55
|
+
replaceInstallationStorageFile(path, content, previous);
|
|
56
|
+
written = true;
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
ok: true, action: 'rail_authority_enrolled', written,
|
|
60
|
+
project_id: artifact.project_id, issuer_id: certificate.issuer_id,
|
|
61
|
+
certificate_type: certificate.certificate_type, certificate_ref: definition.path,
|
|
62
|
+
expires_at: certificate.expires_at,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function enrollProjectEvidenceAuthorityFile({ projectDir, certificatePath, replace = false } = {}) {
|
|
67
|
+
const source = readSafeInstallationStorageFile(certificatePath, {
|
|
68
|
+
label: 'enrollment certificate input', maxBytes: MAX_CERTIFICATE_BYTES,
|
|
69
|
+
unsafeCode: 'UNSAFE_AUTHORITY_CERTIFICATE_INPUT', sizeCode: 'AUTHORITY_CERTIFICATE_SIZE_LIMIT',
|
|
70
|
+
});
|
|
71
|
+
let certificate;
|
|
72
|
+
try { certificate = JSON.parse(source.content); }
|
|
73
|
+
catch { fail('INVALID_AUTHORITY_CERTIFICATE_JSON', 'enrollment certificate must be valid JSON'); }
|
|
74
|
+
return enrollProjectEvidenceAuthority({ projectDir, certificate, replace });
|
|
75
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { canonicalSerialize, sha256 } from '@chati/core';
|
|
2
|
+
import { createPublicKey, verify } from 'node:crypto';
|
|
3
|
+
import { loadRuntimeInstallationV2 } from './runtime-installation-v2.js';
|
|
4
|
+
import { delegatedAuthorityPublicIdentity, signDelegatedAuthorityStatement } from '../installer-v2/installation-authority.js';
|
|
5
|
+
|
|
6
|
+
const REQUEST_DOMAIN = 'chati.evidence-authority-certification-request.v1';
|
|
7
|
+
const CERTIFICATE_TYPES = Object.freeze({
|
|
8
|
+
'rail-evidence': 'chati-rail-evidence-authority', 'clickup-mcp': 'chati-clickup-mcp-authority',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
function fail(code, message) { throw Object.assign(new Error(message), { code }); }
|
|
12
|
+
|
|
13
|
+
/** Creates a public request, never an enrolled certificate or a trusted verdict. */
|
|
14
|
+
export function createEvidenceAuthorityRequest({ projectDir, kind, validForDays = 30, clock = () => new Date(), authorityDirectory } = {}) {
|
|
15
|
+
if (!Object.hasOwn(CERTIFICATE_TYPES, kind ?? '')) fail('INVALID_AUTHORITY_REQUEST_KIND', 'kind must be rail-evidence or clickup-mcp');
|
|
16
|
+
if (!Number.isInteger(validForDays) || validForDays < 1 || validForDays > 90) fail('INVALID_AUTHORITY_REQUEST_LIFETIME', 'requested validity must be 1 to 90 days');
|
|
17
|
+
const artifact = loadRuntimeInstallationV2(projectDir, { clock });
|
|
18
|
+
if (!artifact) fail('V2_INSTALLATION_REQUIRED', 'a certification request requires a verified v2 installation');
|
|
19
|
+
const observed = clock();
|
|
20
|
+
const now = (observed instanceof Date ? observed : new Date(observed)).getTime();
|
|
21
|
+
if (!Number.isFinite(now)) fail('INVALID_AUTHORITY_REQUEST_TIME', 'certification request requires a valid time');
|
|
22
|
+
const identityOptions = { projectDir, projectId: artifact.project_id, kind, authorityDirectory };
|
|
23
|
+
const identity = delegatedAuthorityPublicIdentity({ ...identityOptions, create: true });
|
|
24
|
+
const request = {
|
|
25
|
+
schema_version: 1, request_type: REQUEST_DOMAIN,
|
|
26
|
+
requested_issuer_implementation: 'chati-local-evidence-observer-v1',
|
|
27
|
+
project_binding_digest: identity.project_binding_digest,
|
|
28
|
+
certificate_payload: {
|
|
29
|
+
schema_version: 1, certificate_type: CERTIFICATE_TYPES[kind], project_id: artifact.project_id,
|
|
30
|
+
issuer_id: identity.issuer_id, public_key: identity.public_key,
|
|
31
|
+
not_before: new Date(now).toISOString(), expires_at: new Date(now + validForDays * 86400000).toISOString(),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const proof = signDelegatedAuthorityStatement(request, identityOptions);
|
|
35
|
+
// A concurrent key replacement must not produce a request with mismatched proof.
|
|
36
|
+
if (proof.issuer_id !== identity.issuer_id
|
|
37
|
+
|| !verify(null, Buffer.from(canonicalSerialize(request)), createPublicKey(identity.public_key), Buffer.from(proof.signature, 'base64'))) {
|
|
38
|
+
fail('AUTHORITY_REQUEST_IDENTITY_CHANGED', 'delegated identity changed while creating its public request');
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
ok: true, action: 'rail_authority_certification_requested', certified: false,
|
|
42
|
+
request_id: `authority-request-${sha256(request)}`, request,
|
|
43
|
+
proof_of_possession: proof.signature,
|
|
44
|
+
next_required_action: 'authorized_root_reviews_issuer_and_signs_certificate_then_enroll',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -10,9 +10,10 @@ import { readAgentMemory } from '../memory/agent-memory.js';
|
|
|
10
10
|
import { getRelevantGotchas } from '../memory/gotchas.js';
|
|
11
11
|
import { updateProjectContext } from '../memory/magic-docs.js';
|
|
12
12
|
import { generateContextFiles } from '../config/context-file-generator.js';
|
|
13
|
-
import { existsSync
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { resolveFrameworkDir } from '../utils/framework-dir.js';
|
|
16
|
+
import { listHandoffAgents } from '../utils/handoff-paths.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Execute a full agent-to-agent handoff.
|
|
@@ -228,17 +229,9 @@ export function loadHandoffContext(projectDir, fromAgent) {
|
|
|
228
229
|
* @returns {object[]} Array of handoff summaries
|
|
229
230
|
*/
|
|
230
231
|
export function getHandoffHistory(projectDir) {
|
|
231
|
-
const handoffsDir = join(projectDir, resolveFrameworkDir(projectDir), 'artifacts', 'handoffs');
|
|
232
|
-
|
|
233
|
-
if (!existsSync(handoffsDir)) {
|
|
234
|
-
return [];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
232
|
try {
|
|
238
|
-
const files =
|
|
239
|
-
.
|
|
240
|
-
.map(f => {
|
|
241
|
-
const agentName = f.replace('-handoff.md', '');
|
|
233
|
+
const files = listHandoffAgents(projectDir)
|
|
234
|
+
.map(agentName => {
|
|
242
235
|
const result = loadHandoff(projectDir, agentName);
|
|
243
236
|
|
|
244
237
|
if (!result.loaded) {
|
|
@@ -138,6 +138,24 @@ function loadQaThresholds(projectDir) {
|
|
|
138
138
|
return out;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
export function getQaThreshold(agent, projectDir) {
|
|
142
|
+
return loadQaThresholds(projectDir)[agent] ?? null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** A controller may explicitly reopen completed Planning dependencies, never advance. */
|
|
146
|
+
export function assertQaCorrectionTarget(state, agent, results) {
|
|
147
|
+
if (!results.correctionTarget) return;
|
|
148
|
+
const rejected = results.qaVerdict === 'needs_correction'
|
|
149
|
+
|| (Number.isFinite(results.score) && results.score < getQaThreshold(agent, state.targetDir));
|
|
150
|
+
if (agent !== 'qa-planning' || !rejected
|
|
151
|
+
|| !['detail', 'architect', 'ux'].includes(results.correctionTarget)
|
|
152
|
+
|| !state.completedAgents.includes(results.correctionTarget)
|
|
153
|
+
|| !results.handoffData?.decision_trail_entries?.some(entry => entry.resolved === false)) {
|
|
154
|
+
throw Object.assign(new Error('An explicit QA correction target requires a completed negative review, unresolved findings, and a completed upstream Planning agent.'),
|
|
155
|
+
{ code: 'INVALID_QA_CORRECTION_TARGET' });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
/**
|
|
142
160
|
* Route a failing QA to the correct rework target via Fault Vector Protocol.
|
|
143
161
|
*
|
|
@@ -396,6 +414,7 @@ export function initStandardFlowPipeline(options = {}) {
|
|
|
396
414
|
* @returns {{ state: object, nextAction: string, nextAgent: string|null, needsModeSwitch: boolean, contextBracket: object }}
|
|
397
415
|
*/
|
|
398
416
|
export function advancePipeline(pipelineState, completedAgent, results = {}) {
|
|
417
|
+
assertQaCorrectionTarget(pipelineState, completedAgent, results);
|
|
399
418
|
const newState = { ...pipelineState };
|
|
400
419
|
|
|
401
420
|
// Update completed agent status
|
|
@@ -406,7 +425,7 @@ export function advancePipeline(pipelineState, completedAgent, results = {}) {
|
|
|
406
425
|
newState.agents[completedAgent] = {
|
|
407
426
|
...newState.agents[completedAgent],
|
|
408
427
|
status: AGENT_STATUS.COMPLETED,
|
|
409
|
-
score: results.score
|
|
428
|
+
score: results.score ?? null,
|
|
410
429
|
completedAt: new Date().toISOString(),
|
|
411
430
|
};
|
|
412
431
|
|
|
@@ -426,15 +445,30 @@ export function advancePipeline(pipelineState, completedAgent, results = {}) {
|
|
|
426
445
|
agent: completedAgent,
|
|
427
446
|
action: 'completed',
|
|
428
447
|
timestamp: new Date().toISOString(),
|
|
429
|
-
score: results.score
|
|
448
|
+
score: results.score ?? null,
|
|
430
449
|
});
|
|
431
450
|
|
|
432
451
|
// Special handling for QA agents - check if they meet thresholds
|
|
433
452
|
const isQAAgent = completedAgent === 'qa-planning' || completedAgent === 'qa-implementation' || completedAgent === 'qa-visual';
|
|
434
453
|
if (isQAAgent) {
|
|
435
454
|
const transitionCheck = checkPhaseTransition(newState);
|
|
455
|
+
const rejected = results.qaVerdict === 'needs_correction'
|
|
456
|
+
|| !Number.isFinite(results.score)
|
|
457
|
+
|| results.score < getQaThreshold(completedAgent, newState.targetDir);
|
|
458
|
+
|
|
459
|
+
// QA completion precedes Phases/Tasks in Team Mode. Missing later agents
|
|
460
|
+
// prevent a phase transition, not an otherwise approved QA result.
|
|
461
|
+
if (completedAgent === 'qa-planning' && newState.phase === 'plan'
|
|
462
|
+
&& !rejected && !transitionCheck.canAdvance
|
|
463
|
+
&& ['phases', 'tasks'].some(agent => newState.agents[agent]?.status !== AGENT_STATUS.COMPLETED)) {
|
|
464
|
+
return {
|
|
465
|
+
state: newState, nextAction: 'continue',
|
|
466
|
+
nextAgent: getNextAgent(completedAgent, newState.completedAgents).next,
|
|
467
|
+
needsModeSwitch: false, contextBracket,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
436
470
|
|
|
437
|
-
if (transitionCheck.canAdvance) {
|
|
471
|
+
if (!rejected && transitionCheck.canAdvance) {
|
|
438
472
|
// User Preview gate: when the final QA gate (qa-visual) passes in build phase,
|
|
439
473
|
// don't advance to deploy yet — let user preview first. (4.3.0+: qa-visual is
|
|
440
474
|
// the final build gate; previously this was qa-implementation.)
|
|
@@ -491,11 +525,15 @@ export function advancePipeline(pipelineState, completedAgent, results = {}) {
|
|
|
491
525
|
// QA failed — trigger backward transition via Fault Vector Protocol.
|
|
492
526
|
// We re-open the rework target, NOT wait for manual intervention.
|
|
493
527
|
const faultOrigin = (results.faultOrigin || extractPrimaryFaultOrigin(results.handoffData)).toUpperCase();
|
|
494
|
-
const
|
|
528
|
+
const routed = routeByFaultVector(
|
|
495
529
|
completedAgent,
|
|
496
530
|
faultOrigin,
|
|
497
531
|
newState,
|
|
498
532
|
);
|
|
533
|
+
const correctionTarget = results.correctionTarget || routed.nextAgent;
|
|
534
|
+
const routingReason = results.correctionTarget
|
|
535
|
+
? `${completedAgent} requires correction; the controller selected upstream ${correctionTarget} from the preserved findings.`
|
|
536
|
+
: routed.reason;
|
|
499
537
|
|
|
500
538
|
// DEFER or unroutable — log and stay in wait (legacy behavior)
|
|
501
539
|
if (!correctionTarget) {
|