0xray 4.0.41 → 4.0.42
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +12 -0
- package/.xray/features.json +1 -1
- package/.xray/features.schema.json +1 -1
- package/AGENTS-consumer.md +2 -0
- package/AGENTS.md +2 -0
- package/README.md +52 -12
- package/SKILLS.md +2 -0
- package/dist/AGENTS.md +2 -0
- package/dist/CHANGELOG.md +13 -0
- package/dist/README.md +52 -12
- package/dist/SKILLS.md +2 -0
- package/dist/cli/commands/openclaw-install.js +1 -1
- package/dist/cli/index.js +12 -0
- package/dist/enforcement/index.d.ts +4 -0
- package/dist/enforcement/index.js +4 -0
- package/dist/enforcement/validators/code-quality-validators.js +6 -4
- package/dist/governance/llm-governance-provider.d.ts +7 -1
- package/dist/governance/llm-governance-provider.js +72 -14
- package/dist/governance/local-confer.d.ts +4 -1
- package/dist/governance/local-confer.js +22 -1
- package/dist/integrations/grok/grok-cli.js +10 -37
- package/dist/integrations/grok/hooks/grok-hook-utils.js +8 -1
- package/dist/integrations/hooks/confer-hook-runtime.mjs +23 -1
- package/dist/integrations/hooks/goggles-planes.json +4 -1
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/architect-tools.server.d.ts +6 -0
- package/dist/mcps/architect-tools.server.js +19 -2
- package/dist/mcps/config/server-config-registry.js +8 -6
- package/dist/mcps/governance.server.d.ts +1 -0
- package/dist/mcps/governance.server.js +46 -0
- package/dist/mcps/knowledge-skills/code-review.server.js +7 -0
- package/dist/mcps/knowledge-skills/security-audit.server.js +5 -3
- package/dist/mcps/mcp-client.js +1 -1
- package/dist/mcps/orchestrator/handlers/complexity-handler.js +2 -2
- package/dist/mcps/researcher.server.js +12 -3
- package/dist/nucleus/autonomy-kernel.d.ts +2 -0
- package/dist/nucleus/autonomy-kernel.js +17 -13
- package/dist/nucleus/confer-host.d.ts +35 -0
- package/dist/nucleus/confer-host.js +55 -0
- package/dist/nucleus/confer.d.ts +19 -3
- package/dist/nucleus/confer.js +111 -55
- package/dist/nucleus/lead-dev-plan-persistence.js +4 -2
- package/dist/nucleus/suit-temperament.d.ts +1 -1
- package/dist/nucleus/suit-temperament.js +2 -2
- package/dist/nucleus/synthesis-consult-receipt.d.ts +7 -1
- package/dist/nucleus/synthesis-consult-receipt.js +86 -35
- package/dist/reporting/framework-reporting-system.js +2 -5
- package/dist/reporting/log-parser.d.ts +2 -0
- package/dist/reporting/log-parser.js +7 -6
- package/dist/skills/orchestrator/SKILL.md +12 -0
- package/docs-site/docs/plates/activity-log.md +1 -1
- package/docs-site/docs/plates/boot.md +1 -1
- package/docs-site/docs/plates/centrifuge.md +1 -1
- package/docs-site/docs/plates/cursor-compact.md +1 -1
- package/docs-site/docs/plates/glossary.md +1 -1
- package/docs-site/docs/plates/goggles.md +1 -1
- package/docs-site/docs/plates/governance.md +1 -1
- package/docs-site/docs/plates/grok-compact.md +1 -1
- package/docs-site/docs/plates/grokbot.md +1 -1
- package/docs-site/docs/plates/host-pack.md +1 -1
- package/docs-site/docs/plates/house.md +1 -1
- package/docs-site/docs/plates/index.md +1 -1
- package/docs-site/docs/plates/inference-files.md +1 -1
- package/docs-site/docs/plates/kept-line.md +1 -1
- package/docs-site/docs/plates/kits.md +1 -1
- package/docs-site/docs/plates/lens-gate.md +1 -1
- package/docs-site/docs/plates/lens-page.md +1 -1
- package/docs-site/docs/plates/live-mesh.md +1 -1
- package/docs-site/docs/plates/memory-recall.md +1 -1
- package/docs-site/docs/plates/notes-page.md +1 -1
- package/docs-site/docs/plates/orchestration.md +1 -1
- package/docs-site/docs/plates/package-face.md +1 -1
- package/docs-site/docs/plates/payload-heat.md +1 -1
- package/docs-site/docs/plates/pickup-stamp.md +1 -1
- package/docs-site/docs/plates/pre-tool.md +1 -1
- package/docs-site/docs/plates/processor.md +1 -1
- package/docs-site/docs/plates/record-map.md +1 -1
- package/docs-site/docs/plates/reflection-page.md +1 -1
- package/docs-site/docs/plates/reporting.md +1 -1
- package/docs-site/docs/plates/review.md +1 -1
- package/docs-site/docs/plates/routing.md +1 -1
- package/docs-site/docs/plates/session-capture.md +1 -1
- package/docs-site/docs/plates/site-manual.md +1 -1
- package/docs-site/docs/plates/stamp-plate.md +1 -1
- package/docs-site/docs/plates/station-card.md +1 -1
- package/docs-site/docs/plates/station-heat.md +1 -1
- package/docs-site/docs/plates/suit-organs.md +1 -1
- package/docs-site/docs/plates/suit-settings.md +1 -1
- package/docs-site/docs/plates/suit-wear.md +1 -1
- package/docs-site/docs/plates/suit.md +1 -1
- package/docs-site/docs/plates/trail-state.md +1 -1
- package/docs-site/docs/plates/work-fresh.md +1 -1
- package/docs-site/docs/plates/write-home.md +1 -1
- package/llms.txt +2 -0
- package/package.json +2 -2
- package/scripts/mjs/verify-confer-core.mjs +153 -29
- package/scripts/mjs/verify-user-aside-core.mjs +3 -4
- package/scripts/node/bridge-mcp-wiring.cjs +236 -5
- package/scripts/node/confer-switch.cjs +66 -0
- package/scripts/node/install-bridges.cjs +16 -41
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/architect-tools.server.ts +26 -2
- package/src/mcps/config/__tests__/server-config-registry.test.ts +15 -6
- package/src/mcps/config/server-config-registry.ts +9 -6
- package/src/mcps/governance.server.ts +49 -0
- package/src/mcps/knowledge-skills/code-review.server.ts +8 -0
- package/src/mcps/knowledge-skills/security-audit.server.ts +11 -12
- package/src/mcps/mcp-client.ts +1 -1
- package/src/mcps/orchestrator/handlers/complexity-handler.ts +2 -1
- package/src/mcps/researcher.server.ts +19 -10
- package/src/skills/orchestrator/SKILL.md +12 -0
- package/vendor/@0xray/repertoire/scripts/run-live-synthesis-checkpoint.mjs +5 -51
- package/xray/features.json +1 -1
- package/xray/features.schema.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confer host port. The wearing TUI already spawned the MCP servers.
|
|
3
|
+
* This port writes the ask or returns the host's answer. It does not spawn a server.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export function conferAskPath(todoId, projectRoot) {
|
|
8
|
+
return path.join(projectRoot, '.xray', 'state', `confer-ask-${todoId}.json`);
|
|
9
|
+
}
|
|
10
|
+
/** Evidence for the host model. The schema line is not a vote. */
|
|
11
|
+
export function buildHostConferEvidence(role, evidence) {
|
|
12
|
+
const body = evidence.trim();
|
|
13
|
+
return [
|
|
14
|
+
`## Confer evidence — ${role}`,
|
|
15
|
+
'',
|
|
16
|
+
body || 'No local evidence. The wearing TUI has the confer ask.',
|
|
17
|
+
'',
|
|
18
|
+
'The wearing TUI writes the verdict on its own line after this evidence.',
|
|
19
|
+
'',
|
|
20
|
+
'Verdict: PASS | CONDITIONAL | FAIL',
|
|
21
|
+
'- Top risks (bulleted)',
|
|
22
|
+
'- Hardening note / recommendations',
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
export class SessionConferHost {
|
|
26
|
+
async call(seat, projectRoot) {
|
|
27
|
+
const askPath = conferAskPath(seat.todoId, projectRoot);
|
|
28
|
+
fs.mkdirSync(path.dirname(askPath), { recursive: true });
|
|
29
|
+
const payload = {
|
|
30
|
+
todoId: seat.todoId,
|
|
31
|
+
subagent: seat.subagent,
|
|
32
|
+
server: seat.server,
|
|
33
|
+
tool: seat.tool,
|
|
34
|
+
args: seat.args,
|
|
35
|
+
sessionId: seat.sessionId,
|
|
36
|
+
prompt: seat.prompt,
|
|
37
|
+
dueReason: seat.dueReason,
|
|
38
|
+
};
|
|
39
|
+
fs.writeFileSync(askPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
40
|
+
return { status: 'pending', askPath };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class InProcessConferHost {
|
|
44
|
+
answers;
|
|
45
|
+
constructor(answers) {
|
|
46
|
+
this.answers = answers;
|
|
47
|
+
}
|
|
48
|
+
async call(seat) {
|
|
49
|
+
const text = this.answers[seat.todoId] ?? this.answers[seat.subagent];
|
|
50
|
+
if (text === undefined) {
|
|
51
|
+
return { status: 'pending', askPath: '' };
|
|
52
|
+
}
|
|
53
|
+
return { status: 'answered', text };
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/nucleus/confer.d.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Triggered automatically at synthesis checkpoint; records consult receipts + plan todos.
|
|
4
4
|
*/
|
|
5
5
|
import { type SynthesisConsultReceipt } from './synthesis-consult-receipt.js';
|
|
6
|
+
import { InProcessConferHost, SessionConferHost, type ConferHostPort, type ConferHostSeat } from './confer-host.js';
|
|
7
|
+
export { InProcessConferHost, SessionConferHost };
|
|
8
|
+
export type { ConferHostPort, ConferHostSeat };
|
|
6
9
|
export declare const CONFER_AGENTS: readonly ["researcher", "architect-tools", "code-review"];
|
|
7
10
|
export interface ConferConfig {
|
|
8
11
|
enabled: boolean;
|
|
@@ -27,12 +30,16 @@ export interface ConferAgentResult {
|
|
|
27
30
|
error?: string;
|
|
28
31
|
}
|
|
29
32
|
export interface ConferQuorumResult {
|
|
30
|
-
status: 'completed' | 'partial' | 'skipped' | 'failed';
|
|
33
|
+
status: 'completed' | 'partial' | 'skipped' | 'failed' | 'pending';
|
|
31
34
|
agents: ConferAgentResult[];
|
|
32
35
|
message: string;
|
|
33
36
|
}
|
|
34
37
|
export declare function conferCheckpointPath(projectRoot?: string): string;
|
|
35
38
|
export declare function defaultConferConfig(): ConferConfig;
|
|
39
|
+
/** Fixture PASS is a test harness. Live orchestrate-task and XRAY_CONFER_FIXTURE cannot reach it. */
|
|
40
|
+
export declare const CONFER_FIXTURE_UNREACHABLE = "Confer fixture is unreachable outside tests";
|
|
41
|
+
export declare function conferFixtureAllowed(): boolean;
|
|
42
|
+
/** A suit profile does not enable Confer. Missing confer key stays off, including strict. */
|
|
36
43
|
export declare function loadConferConfig(projectRoot?: string): ConferConfig;
|
|
37
44
|
export declare function isConferEnabled(projectRoot?: string): boolean;
|
|
38
45
|
export declare function loadConferCheckpoint(projectRoot?: string): ConferCheckpointState | null;
|
|
@@ -46,12 +53,21 @@ export declare function conferAgentMcpTarget(subagent: string): {
|
|
|
46
53
|
tool: string;
|
|
47
54
|
args: (prompt: string, projectRoot: string) => Record<string, unknown>;
|
|
48
55
|
};
|
|
49
|
-
export declare function invokeConferAgent(subagent: string, prompt: string, projectRoot?: string): Promise<string>;
|
|
56
|
+
export declare function invokeConferAgent(subagent: string, prompt: string, projectRoot?: string, host?: ConferHostPort, sessionId?: string, todoId?: string, dueReason?: string | null): Promise<string>;
|
|
50
57
|
export declare function applyConferConsultResult(todoId: string, subagent: string, sessionId: string, outputText: string, projectRoot?: string): ConferAgentResult;
|
|
51
|
-
export declare function
|
|
58
|
+
export declare function buildFixtureConferOutput(subagent: string, verdict?: SynthesisConsultReceipt['verdict'], noLlm?: boolean): string;
|
|
59
|
+
export declare function writeFixtureConferReceipt(todoId: string, subagent: string, sessionId: string, projectRoot?: string, verdict?: SynthesisConsultReceipt['verdict'], noLlm?: boolean): ConferAgentResult;
|
|
52
60
|
export declare function runConferQuorum(projectRoot: string | undefined, sessionId: string, options?: {
|
|
53
61
|
collocatedText?: string;
|
|
54
62
|
dueReason?: string | null;
|
|
55
63
|
fixture?: boolean;
|
|
64
|
+
/** Used only when fixture is true. Defaults to PASS. FAIL and UNREVIEWED do not complete the todo. */
|
|
65
|
+
fixtureVerdict?: SynthesisConsultReceipt['verdict'];
|
|
66
|
+
/** Fixture text is the real no-model localConferVote (UNREVIEWED), not a hardcoded PASS. */
|
|
67
|
+
fixtureNoLlm?: boolean;
|
|
68
|
+
/** Used only when fixture is true. Replaces the generated fixture text for every consult. */
|
|
69
|
+
fixtureOutput?: string;
|
|
70
|
+
/** Live path. Default writes an ask for the wearing TUI and does not spawn a server. */
|
|
71
|
+
host?: ConferHostPort;
|
|
56
72
|
}): Promise<ConferQuorumResult>;
|
|
57
73
|
export declare function formatConferQuorumReport(result: ConferQuorumResult): string;
|
package/dist/nucleus/confer.js
CHANGED
|
@@ -6,45 +6,41 @@ import * as fs from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { MANDATORY_MAJOR_CONSULTS } from './autonomy-kernel.js';
|
|
8
8
|
import { areSynthesisConsultTodosComplete, getNextRequiredTodo, getSynthesisConsultTodos, isSynthesisRealignmentPlan, loadPersistedLeadDevPlan, updatePlanTodoStatus, } from './lead-dev-plan-persistence.js';
|
|
9
|
-
import { buildReceiptFromConsultOutput, tryRecordSynthesisConsultReceipt, parseConsultVerdictFromText, } from './synthesis-consult-receipt.js';
|
|
9
|
+
import { buildReceiptFromConsultOutput, consultVerdictBlocksCompletion, loadSynthesisConsultReceipt, tryRecordSynthesisConsultReceipt, parseConsultVerdictFromText, } from './synthesis-consult-receipt.js';
|
|
10
|
+
import { formatGovernanceVoteText, localConferVote } from '../governance/local-confer.js';
|
|
10
11
|
import { isSynthesisCheckpointDue } from './synthesis.js';
|
|
11
|
-
import {
|
|
12
|
+
import { InProcessConferHost, SessionConferHost, } from './confer-host.js';
|
|
13
|
+
export { InProcessConferHost, SessionConferHost };
|
|
12
14
|
export const CONFER_AGENTS = [...MANDATORY_MAJOR_CONSULTS];
|
|
13
15
|
const STATE_VERSION = 1;
|
|
14
16
|
export function conferCheckpointPath(projectRoot = process.cwd()) {
|
|
15
17
|
return path.join(projectRoot, '.xray', 'state', 'confer-checkpoint.json');
|
|
16
18
|
}
|
|
17
19
|
export function defaultConferConfig() {
|
|
18
|
-
return { enabled:
|
|
20
|
+
return { enabled: false, on_synthesis: false };
|
|
19
21
|
}
|
|
22
|
+
/** Fixture PASS is a test harness. Live orchestrate-task and XRAY_CONFER_FIXTURE cannot reach it. */
|
|
23
|
+
export const CONFER_FIXTURE_UNREACHABLE = 'Confer fixture is unreachable outside tests';
|
|
24
|
+
export function conferFixtureAllowed() {
|
|
25
|
+
return process.env.NODE_ENV === 'test' || Boolean(process.env.VITEST);
|
|
26
|
+
}
|
|
27
|
+
/** A suit profile does not enable Confer. Missing confer key stays off, including strict. */
|
|
20
28
|
export function loadConferConfig(projectRoot = process.cwd()) {
|
|
21
29
|
const featuresPath = path.join(projectRoot, '.xray', 'features.json');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return { enabled: true, on_synthesis: true };
|
|
25
|
-
}
|
|
26
|
-
if (!fs.existsSync(featuresPath)) {
|
|
27
|
-
return conferDefaultForProfile(profile)
|
|
28
|
-
? defaultConferConfig()
|
|
29
|
-
: { enabled: false, on_synthesis: false };
|
|
30
|
-
}
|
|
30
|
+
if (!fs.existsSync(featuresPath))
|
|
31
|
+
return defaultConferConfig();
|
|
31
32
|
try {
|
|
32
33
|
const data = JSON.parse(fs.readFileSync(featuresPath, 'utf8'));
|
|
33
34
|
const orch = data.multi_agent_orchestration ?? {};
|
|
34
35
|
const raw = orch.confer ?? {};
|
|
35
|
-
|
|
36
|
-
const optedIn = raw.enabled === true || orch.confer_on_synthesis === true;
|
|
37
|
-
return { enabled: optedIn, on_synthesis: optedIn };
|
|
38
|
-
}
|
|
36
|
+
const optedIn = raw.enabled === true || orch.confer_on_synthesis === true;
|
|
39
37
|
return {
|
|
40
|
-
enabled:
|
|
41
|
-
on_synthesis:
|
|
38
|
+
enabled: optedIn,
|
|
39
|
+
on_synthesis: optedIn && raw.on_synthesis !== false,
|
|
42
40
|
};
|
|
43
41
|
}
|
|
44
42
|
catch {
|
|
45
|
-
return
|
|
46
|
-
? defaultConferConfig()
|
|
47
|
-
: { enabled: false, on_synthesis: false };
|
|
43
|
+
return defaultConferConfig();
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
export function isConferEnabled(projectRoot = process.cwd()) {
|
|
@@ -116,7 +112,7 @@ export function conferAgentMcpTarget(subagent) {
|
|
|
116
112
|
switch (subagent) {
|
|
117
113
|
case 'architect-tools':
|
|
118
114
|
return {
|
|
119
|
-
server: 'architect-tools',
|
|
115
|
+
server: 'xray-architect-tools',
|
|
120
116
|
tool: 'architecture-assessment',
|
|
121
117
|
args: (prompt, projectRoot) => ({
|
|
122
118
|
projectRoot,
|
|
@@ -127,7 +123,7 @@ export function conferAgentMcpTarget(subagent) {
|
|
|
127
123
|
};
|
|
128
124
|
case 'researcher':
|
|
129
125
|
return {
|
|
130
|
-
server: 'researcher',
|
|
126
|
+
server: 'xray-researcher',
|
|
131
127
|
tool: 'analyze_proposal',
|
|
132
128
|
args: (prompt) => ({
|
|
133
129
|
proposalTitle: 'Synthesis confer — researcher',
|
|
@@ -139,7 +135,7 @@ export function conferAgentMcpTarget(subagent) {
|
|
|
139
135
|
case 'code-review':
|
|
140
136
|
default:
|
|
141
137
|
return {
|
|
142
|
-
server: 'code-review',
|
|
138
|
+
server: 'xray-code-review',
|
|
143
139
|
tool: 'analyze_proposal',
|
|
144
140
|
args: (prompt) => ({
|
|
145
141
|
proposalTitle: 'Synthesis confer — code-review',
|
|
@@ -150,38 +146,37 @@ export function conferAgentMcpTarget(subagent) {
|
|
|
150
146
|
};
|
|
151
147
|
}
|
|
152
148
|
}
|
|
153
|
-
function
|
|
154
|
-
const content = result?.content;
|
|
155
|
-
if (Array.isArray(content)) {
|
|
156
|
-
return content.map((c) => c.text ?? '').join('\n');
|
|
157
|
-
}
|
|
158
|
-
if (typeof result === 'string')
|
|
159
|
-
return result;
|
|
160
|
-
try {
|
|
161
|
-
return JSON.stringify(result);
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
return String(result);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
export async function invokeConferAgent(subagent, prompt, projectRoot = process.cwd()) {
|
|
168
|
-
const { mcpClientManager } = await import('../mcps/mcp-client.js');
|
|
149
|
+
function conferHostSeat(subagent, prompt, projectRoot, sessionId, todoId, dueReason) {
|
|
169
150
|
const target = conferAgentMcpTarget(subagent);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
151
|
+
return {
|
|
152
|
+
todoId,
|
|
153
|
+
subagent,
|
|
154
|
+
server: target.server,
|
|
155
|
+
tool: target.tool,
|
|
156
|
+
args: target.args(prompt, projectRoot),
|
|
157
|
+
sessionId,
|
|
158
|
+
prompt,
|
|
159
|
+
dueReason,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export async function invokeConferAgent(subagent, prompt, projectRoot = process.cwd(), host = new SessionConferHost(), sessionId = '', todoId = subagent, dueReason = null) {
|
|
163
|
+
const call = await host.call(conferHostSeat(subagent, prompt, projectRoot, sessionId, todoId, dueReason), projectRoot);
|
|
164
|
+
if (call.status === 'pending') {
|
|
165
|
+
throw new Error(`Confer ask pending for ${subagent} at ${call.askPath}`);
|
|
166
|
+
}
|
|
167
|
+
const text = call.text;
|
|
173
168
|
if (!text.trim()) {
|
|
174
169
|
throw new Error(`Empty confer response from ${subagent}`);
|
|
175
170
|
}
|
|
176
171
|
if (!parseConsultVerdictFromText(text)) {
|
|
177
|
-
throw new Error(`Confer response from ${subagent} missing parseable verdict (expected Verdict: PASS|CONDITIONAL|FAIL or DECISION: approve|reject|abstain)`);
|
|
172
|
+
throw new Error(`Confer response from ${subagent} missing parseable verdict (expected an explicit Verdict: PASS|CONDITIONAL|FAIL|UNREVIEWED line, or DECISION: approve|reject|abstain)`);
|
|
178
173
|
}
|
|
179
174
|
return text;
|
|
180
175
|
}
|
|
181
176
|
export function applyConferConsultResult(todoId, subagent, sessionId, outputText, projectRoot = process.cwd()) {
|
|
182
177
|
const receipt = tryRecordSynthesisConsultReceipt(todoId, subagent, sessionId, outputText, projectRoot);
|
|
183
178
|
let todoCompleted = false;
|
|
184
|
-
if (receipt && receipt.verdict
|
|
179
|
+
if (receipt && !consultVerdictBlocksCompletion(receipt.verdict)) {
|
|
185
180
|
todoCompleted = updatePlanTodoStatus(todoId, 'completed', projectRoot);
|
|
186
181
|
}
|
|
187
182
|
return {
|
|
@@ -193,11 +188,24 @@ export function applyConferConsultResult(todoId, subagent, sessionId, outputText
|
|
|
193
188
|
verdict: receipt?.verdict ?? buildReceiptFromConsultOutput(todoId, subagent, sessionId, outputText)?.verdict ?? null,
|
|
194
189
|
};
|
|
195
190
|
}
|
|
196
|
-
export function
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
export function buildFixtureConferOutput(subagent, verdict = 'PASS', noLlm = false) {
|
|
192
|
+
if (noLlm) {
|
|
193
|
+
const role = subagent === 'code-review' ? 'code-review' : 'researcher';
|
|
194
|
+
return formatGovernanceVoteText(localConferVote({ role, llmConfigured: false }));
|
|
195
|
+
}
|
|
196
|
+
return `Verdict: ${verdict}\nTop risks: none\nHardening: confer fixture quorum for ${subagent}`;
|
|
197
|
+
}
|
|
198
|
+
export function writeFixtureConferReceipt(todoId, subagent, sessionId, projectRoot = process.cwd(), verdict = 'PASS', noLlm = false) {
|
|
199
|
+
return applyConferConsultResult(todoId, subagent, sessionId, buildFixtureConferOutput(subagent, verdict, noLlm), projectRoot);
|
|
199
200
|
}
|
|
200
201
|
export async function runConferQuorum(projectRoot = process.cwd(), sessionId, options = {}) {
|
|
202
|
+
if (options.fixture && !conferFixtureAllowed()) {
|
|
203
|
+
return {
|
|
204
|
+
status: 'failed',
|
|
205
|
+
agents: [],
|
|
206
|
+
message: CONFER_FIXTURE_UNREACHABLE,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
201
209
|
const cfg = loadConferConfig(projectRoot);
|
|
202
210
|
if (!cfg.enabled || !cfg.on_synthesis) {
|
|
203
211
|
return {
|
|
@@ -214,7 +222,7 @@ export async function runConferQuorum(projectRoot = process.cwd(), sessionId, op
|
|
|
214
222
|
message: 'No synthesis realignment plan — confer not applicable',
|
|
215
223
|
};
|
|
216
224
|
}
|
|
217
|
-
if (areSynthesisConsultTodosComplete(plan)) {
|
|
225
|
+
if (areSynthesisConsultTodosComplete(plan) && consultReceiptsAreRealApproves(plan, sessionId, projectRoot)) {
|
|
218
226
|
return {
|
|
219
227
|
status: 'completed',
|
|
220
228
|
agents: [],
|
|
@@ -228,17 +236,36 @@ export async function runConferQuorum(projectRoot = process.cwd(), sessionId, op
|
|
|
228
236
|
'# Synthesis confer\n\nReview checkpoint context and realign plan.';
|
|
229
237
|
const agents = [];
|
|
230
238
|
for (const todo of getSynthesisConsultTodos(plan)) {
|
|
231
|
-
if (todo.status === 'completed')
|
|
239
|
+
if (todo.status === 'completed' && consultReceiptIsRealPass(todo.id, sessionId, plan, projectRoot)) {
|
|
232
240
|
continue;
|
|
241
|
+
}
|
|
242
|
+
if (todo.status === 'completed') {
|
|
243
|
+
updatePlanTodoStatus(todo.id, 'pending', projectRoot);
|
|
244
|
+
}
|
|
233
245
|
try {
|
|
234
246
|
let agentResult;
|
|
235
247
|
if (options.fixture) {
|
|
236
|
-
agentResult =
|
|
248
|
+
agentResult =
|
|
249
|
+
options.fixtureOutput !== undefined
|
|
250
|
+
? applyConferConsultResult(todo.id, todo.subagent, sessionId, options.fixtureOutput, projectRoot)
|
|
251
|
+
: writeFixtureConferReceipt(todo.id, todo.subagent, sessionId, projectRoot, options.fixtureVerdict ?? 'PASS', options.fixtureNoLlm === true);
|
|
237
252
|
}
|
|
238
253
|
else {
|
|
239
254
|
const prompt = buildConferPrompt(todo.subagent, todo.task, collocated, options.dueReason ?? null);
|
|
240
|
-
const
|
|
241
|
-
|
|
255
|
+
const host = options.host ?? new SessionConferHost();
|
|
256
|
+
const call = await host.call(conferHostSeat(todo.subagent, prompt, projectRoot, sessionId, todo.id, options.dueReason ?? null), projectRoot);
|
|
257
|
+
if (call.status === 'pending') {
|
|
258
|
+
agents.push({
|
|
259
|
+
todoId: todo.id,
|
|
260
|
+
subagent: todo.subagent,
|
|
261
|
+
outputText: '',
|
|
262
|
+
receiptRecorded: false,
|
|
263
|
+
todoCompleted: false,
|
|
264
|
+
verdict: null,
|
|
265
|
+
});
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
agentResult = applyConferConsultResult(todo.id, todo.subagent, sessionId, call.text, projectRoot);
|
|
242
269
|
}
|
|
243
270
|
agents.push(agentResult);
|
|
244
271
|
if (agentResult.receiptRecorded && agentResult.todoCompleted) {
|
|
@@ -247,7 +274,9 @@ export async function runConferQuorum(projectRoot = process.cwd(), sessionId, op
|
|
|
247
274
|
}
|
|
248
275
|
else {
|
|
249
276
|
state.status = 'failed';
|
|
250
|
-
state.lastError =
|
|
277
|
+
state.lastError = consultVerdictBlocksCompletion(agentResult.verdict)
|
|
278
|
+
? `${agentResult.verdict} blocks ${todo.id}`
|
|
279
|
+
: `Receipt or todo completion failed for ${todo.id}`;
|
|
251
280
|
saveConferCheckpoint(state, projectRoot);
|
|
252
281
|
return {
|
|
253
282
|
status: 'partial',
|
|
@@ -273,10 +302,21 @@ export async function runConferQuorum(projectRoot = process.cwd(), sessionId, op
|
|
|
273
302
|
return { status: 'failed', agents, message };
|
|
274
303
|
}
|
|
275
304
|
}
|
|
305
|
+
if (agents.some((agent) => agent.verdict === null && !agent.receiptRecorded && !agent.error)) {
|
|
306
|
+
state.status = 'pending';
|
|
307
|
+
delete state.lastError;
|
|
308
|
+
saveConferCheckpoint(state, projectRoot);
|
|
309
|
+
return {
|
|
310
|
+
status: 'pending',
|
|
311
|
+
agents,
|
|
312
|
+
message: 'Confer asks written. The wearing TUI calls its MCP servers and records each verdict.',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
276
315
|
const refreshed = loadPersistedLeadDevPlan(projectRoot);
|
|
277
316
|
const done = refreshed &&
|
|
278
317
|
isSynthesisRealignmentPlan(refreshed) &&
|
|
279
|
-
areSynthesisConsultTodosComplete(refreshed)
|
|
318
|
+
areSynthesisConsultTodosComplete(refreshed) &&
|
|
319
|
+
consultReceiptsAreRealApproves(refreshed, sessionId, projectRoot);
|
|
280
320
|
state.status = done ? 'completed' : 'failed';
|
|
281
321
|
if (!done)
|
|
282
322
|
state.lastError = 'Consult todos remain after confer loop';
|
|
@@ -289,6 +329,22 @@ export async function runConferQuorum(projectRoot = process.cwd(), sessionId, op
|
|
|
289
329
|
: (state.lastError ?? 'Confer incomplete'),
|
|
290
330
|
};
|
|
291
331
|
}
|
|
332
|
+
function consultReceiptIsRealPass(todoId, sessionId, plan, projectRoot) {
|
|
333
|
+
const receipt = loadSynthesisConsultReceipt(todoId, projectRoot);
|
|
334
|
+
if (receipt?.verdict !== 'PASS')
|
|
335
|
+
return false;
|
|
336
|
+
if (sessionId && receipt.sessionId !== sessionId)
|
|
337
|
+
return false;
|
|
338
|
+
if (plan.consultCycleId && receipt.cycleId !== plan.consultCycleId)
|
|
339
|
+
return false;
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
function consultReceiptsAreRealApproves(plan, sessionId, projectRoot) {
|
|
343
|
+
const todos = getSynthesisConsultTodos(plan);
|
|
344
|
+
if (todos.length === 0)
|
|
345
|
+
return false;
|
|
346
|
+
return todos.every((todo) => consultReceiptIsRealPass(todo.id, sessionId, plan, projectRoot));
|
|
347
|
+
}
|
|
292
348
|
const CONFER_AGENT_EMOJI = {
|
|
293
349
|
researcher: '🔍',
|
|
294
350
|
'architect-tools': '🏗️',
|
|
@@ -7,7 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import { SYNTHESIS_REALIGNMENT_PHASE_ID } from './autonomy-kernel.js';
|
|
8
8
|
import { DEFAULT_PENDING_TTL_MS } from './pending-delegations.js';
|
|
9
9
|
import { completeSynthesisCheckpoint, getSynthesisCheckpointSessionId, isSynthesisCheckpointDue, recordExecutionSlice, } from './synthesis.js';
|
|
10
|
-
import { hasValidSynthesisConsultReceipt, isSynthesisConsultTodoId, loadSynthesisConsultReceipt, } from './synthesis-consult-receipt.js';
|
|
10
|
+
import { consultVerdictBlocksCompletion, hasValidSynthesisConsultReceipt, isSynthesisConsultTodoId, loadSynthesisConsultReceipt, } from './synthesis-consult-receipt.js';
|
|
11
11
|
import { resolveSpawnPlan } from './spawn-plan-resolution.js';
|
|
12
12
|
import { isAsideActiveForSession, isUserAsideTodoId, isUserAsidesEnabled, parseAsideIdFromTodoId, findAsideContainingTodo, updateUserAsideTodoStatus, } from './user-aside.js';
|
|
13
13
|
export const DEFAULT_PLAN_STALE_MS = 8 * 60 * 60 * 1000;
|
|
@@ -259,11 +259,13 @@ export function updatePlanTodoStatus(todoId, status, projectRoot = process.cwd()
|
|
|
259
259
|
receiptExpected.sessionId = plan.sessionId;
|
|
260
260
|
if (targetTodo?.subagent)
|
|
261
261
|
receiptExpected.subagent = targetTodo.subagent;
|
|
262
|
+
if (plan.consultCycleId)
|
|
263
|
+
receiptExpected.cycleId = plan.consultCycleId;
|
|
262
264
|
if (!hasValidSynthesisConsultReceipt(todoId, projectRoot, receiptExpected)) {
|
|
263
265
|
return false;
|
|
264
266
|
}
|
|
265
267
|
const receipt = loadSynthesisConsultReceipt(todoId, projectRoot);
|
|
266
|
-
if (receipt?.verdict
|
|
268
|
+
if (consultVerdictBlocksCompletion(receipt?.verdict)) {
|
|
267
269
|
return false;
|
|
268
270
|
}
|
|
269
271
|
}
|
|
@@ -32,4 +32,4 @@ export declare function maybeHeatHostStation(projectRoot: string, host: SuitHost
|
|
|
32
32
|
export declare function writeSuitSessionBoot(projectRoot: string, host: SuitHost, extra?: Record<string, unknown>): string;
|
|
33
33
|
export declare function ceremonyForProfile(profile: SuitProfile): CeremonyLevel;
|
|
34
34
|
export declare function spawnPlanModeForProfile(profile: SuitProfile): SpawnPlanMode;
|
|
35
|
-
export declare function conferDefaultForProfile(
|
|
35
|
+
export declare function conferDefaultForProfile(_profile: SuitProfile): boolean;
|
|
@@ -170,6 +170,6 @@ export function spawnPlanModeForProfile(profile) {
|
|
|
170
170
|
return 'warn';
|
|
171
171
|
return 'deny';
|
|
172
172
|
}
|
|
173
|
-
export function conferDefaultForProfile(
|
|
174
|
-
return
|
|
173
|
+
export function conferDefaultForProfile(_profile) {
|
|
174
|
+
return false;
|
|
175
175
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Synthesis consult receipt SSOT — outcome-based completion for s.N todos.
|
|
3
3
|
* Receipts live at `.xray/state/synthesis-consult-{todoId}.json`.
|
|
4
4
|
*/
|
|
5
|
-
export type SynthesisConsultVerdict = 'PASS' | 'CONDITIONAL' | 'FAIL';
|
|
5
|
+
export type SynthesisConsultVerdict = 'PASS' | 'CONDITIONAL' | 'FAIL' | 'UNREVIEWED';
|
|
6
6
|
export interface SynthesisConsultReceipt {
|
|
7
7
|
sessionId: string;
|
|
8
8
|
subagent: string;
|
|
@@ -11,18 +11,24 @@ export interface SynthesisConsultReceipt {
|
|
|
11
11
|
hardeningNote: string;
|
|
12
12
|
todoId?: string;
|
|
13
13
|
recordedAt?: string;
|
|
14
|
+
/** Synthesis plan generation this receipt belongs to. A previous cycle's PASS cannot clear the next s.N. */
|
|
15
|
+
cycleId?: string;
|
|
14
16
|
}
|
|
15
17
|
export declare function isSynthesisConsultTodoId(todoId: string): boolean;
|
|
16
18
|
export declare function synthesisConsultReceiptPath(todoId: string, projectRoot?: string): string;
|
|
17
19
|
export declare function loadSynthesisConsultReceipt(todoId: string, projectRoot?: string): SynthesisConsultReceipt | null;
|
|
18
20
|
export declare function writeSynthesisConsultReceipt(todoId: string, receipt: SynthesisConsultReceipt, projectRoot?: string): string;
|
|
21
|
+
/** Only a real approve (PASS) completes a consult todo. */
|
|
22
|
+
export declare function consultVerdictBlocksCompletion(verdict: SynthesisConsultVerdict | null | undefined): boolean;
|
|
19
23
|
export declare function validateSynthesisConsultReceipt(receipt: SynthesisConsultReceipt, todoId: string, expected?: {
|
|
20
24
|
sessionId?: string | null;
|
|
21
25
|
subagent?: string;
|
|
26
|
+
cycleId?: string;
|
|
22
27
|
}): boolean;
|
|
23
28
|
export declare function hasValidSynthesisConsultReceipt(todoId: string, projectRoot?: string, expected?: {
|
|
24
29
|
sessionId?: string | null;
|
|
25
30
|
subagent?: string;
|
|
31
|
+
cycleId?: string;
|
|
26
32
|
}): boolean;
|
|
27
33
|
export declare function parseConsultVerdictFromText(text: string): SynthesisConsultVerdict | null;
|
|
28
34
|
export declare function extractTopRisksFromText(text: string, max?: number): string[];
|
|
@@ -25,14 +25,30 @@ export function loadSynthesisConsultReceipt(todoId, projectRoot = process.cwd())
|
|
|
25
25
|
export function writeSynthesisConsultReceipt(todoId, receipt, projectRoot = process.cwd()) {
|
|
26
26
|
const receiptPath = synthesisConsultReceiptPath(todoId, projectRoot);
|
|
27
27
|
fs.mkdirSync(path.dirname(receiptPath), { recursive: true });
|
|
28
|
+
const cycleId = receipt.cycleId ?? readPlanConsultCycleId(projectRoot);
|
|
28
29
|
const payload = {
|
|
29
30
|
...receipt,
|
|
30
31
|
todoId,
|
|
31
32
|
recordedAt: receipt.recordedAt ?? new Date().toISOString(),
|
|
33
|
+
...(cycleId ? { cycleId } : {}),
|
|
32
34
|
};
|
|
33
35
|
fs.writeFileSync(receiptPath, JSON.stringify(payload, null, 2));
|
|
34
36
|
return receiptPath;
|
|
35
37
|
}
|
|
38
|
+
function readPlanConsultCycleId(projectRoot) {
|
|
39
|
+
const planPath = path.join(projectRoot, '.xray', 'state', 'lead-dev-plan.json');
|
|
40
|
+
if (!fs.existsSync(planPath))
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
const data = JSON.parse(fs.readFileSync(planPath, 'utf8'));
|
|
44
|
+
return typeof data.consultCycleId === 'string' && data.consultCycleId.trim()
|
|
45
|
+
? data.consultCycleId
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
36
52
|
function normalizeSubagent(agent) {
|
|
37
53
|
const key = agent.toLowerCase().trim();
|
|
38
54
|
const aliases = {
|
|
@@ -47,7 +63,11 @@ function subagentsAlign(expected, actual) {
|
|
|
47
63
|
return e === a || e.includes(a) || a.includes(e);
|
|
48
64
|
}
|
|
49
65
|
function isValidVerdict(value) {
|
|
50
|
-
return value === 'PASS' || value === 'CONDITIONAL' || value === 'FAIL';
|
|
66
|
+
return value === 'PASS' || value === 'CONDITIONAL' || value === 'FAIL' || value === 'UNREVIEWED';
|
|
67
|
+
}
|
|
68
|
+
/** Only a real approve (PASS) completes a consult todo. */
|
|
69
|
+
export function consultVerdictBlocksCompletion(verdict) {
|
|
70
|
+
return verdict !== 'PASS';
|
|
51
71
|
}
|
|
52
72
|
export function validateSynthesisConsultReceipt(receipt, todoId, expected) {
|
|
53
73
|
if (!receipt.sessionId || !receipt.subagent || !isValidVerdict(receipt.verdict)) {
|
|
@@ -62,6 +82,8 @@ export function validateSynthesisConsultReceipt(receipt, todoId, expected) {
|
|
|
62
82
|
if (expected?.subagent && !subagentsAlign(expected.subagent, receipt.subagent)) {
|
|
63
83
|
return false;
|
|
64
84
|
}
|
|
85
|
+
if (expected?.cycleId && receipt.cycleId !== expected.cycleId)
|
|
86
|
+
return false;
|
|
65
87
|
if (receipt.todoId && receipt.todoId !== todoId)
|
|
66
88
|
return false;
|
|
67
89
|
return true;
|
|
@@ -72,52 +94,81 @@ export function hasValidSynthesisConsultReceipt(todoId, projectRoot = process.cw
|
|
|
72
94
|
return false;
|
|
73
95
|
return validateSynthesisConsultReceipt(receipt, todoId, expected);
|
|
74
96
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
const EXPLICIT_VERDICT_LINE = /^Verdict:\s*(PASS|CONDITIONAL|FAIL|UNREVIEWED)\s*$/i;
|
|
98
|
+
const VERDICT_OPTION_ECHO = /\bPASS\s*\|\s*CONDITIONAL\s*\|\s*FAIL\b/i;
|
|
99
|
+
const DECISION_LINE = /^DECISION:\s*(approve|reject|abstain|approved|rejected|pass|fail|needs_revision|conditional|revise)\s*$/i;
|
|
100
|
+
function lineWithoutBullet(rawLine) {
|
|
101
|
+
return rawLine.trim().replace(/^[-*•]\s*/, '');
|
|
102
|
+
}
|
|
103
|
+
function splitAtReasoning(text) {
|
|
104
|
+
const lines = text.split('\n');
|
|
105
|
+
const idx = lines.findIndex((rawLine) => /^REASONING:/i.test(lineWithoutBullet(rawLine)));
|
|
106
|
+
if (idx < 0)
|
|
107
|
+
return { header: text, reasoning: null };
|
|
108
|
+
return {
|
|
109
|
+
header: lines.slice(0, idx).join('\n'),
|
|
110
|
+
reasoning: lines.slice(idx).join('\n'),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Abstain because no governance model ran. A bare DECISION: abstain stays CONDITIONAL. */
|
|
114
|
+
function isAbstainWithoutModel(text) {
|
|
115
|
+
return /nested LLM not configured|did not produce a vote|returned no vote/i.test(text);
|
|
116
|
+
}
|
|
117
|
+
/** First exact DECISION line before REASONING. A menu or a line inside the reasoning body is not a vote. */
|
|
118
|
+
function firstHeaderDecision(text) {
|
|
119
|
+
const { header } = splitAtReasoning(text);
|
|
120
|
+
for (const rawLine of header.split('\n')) {
|
|
121
|
+
const match = lineWithoutBullet(rawLine).match(DECISION_LINE);
|
|
122
|
+
const decision = match?.[1]?.toLowerCase();
|
|
123
|
+
if (!decision)
|
|
124
|
+
continue;
|
|
125
|
+
if (decision === 'approve' || decision === 'approved' || decision === 'pass')
|
|
126
|
+
return 'PASS';
|
|
127
|
+
if (decision === 'reject' || decision === 'rejected' || decision === 'fail')
|
|
128
|
+
return 'FAIL';
|
|
129
|
+
if (decision === 'abstain' && isAbstainWithoutModel(text))
|
|
130
|
+
return 'UNREVIEWED';
|
|
85
131
|
return 'CONDITIONAL';
|
|
86
132
|
}
|
|
87
133
|
return null;
|
|
88
134
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const token = match?.[1];
|
|
96
|
-
if (!token || trimmed.includes('|'))
|
|
135
|
+
function hasAmbiguousVerdictLine(text) {
|
|
136
|
+
for (const rawLine of text.split('\n')) {
|
|
137
|
+
const line = lineWithoutBullet(rawLine);
|
|
138
|
+
if (!/^Verdict:/i.test(line))
|
|
139
|
+
continue;
|
|
140
|
+
if (EXPLICIT_VERDICT_LINE.test(line))
|
|
97
141
|
continue;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
function lastExplicitVerdictLine(text) {
|
|
147
|
+
let found = null;
|
|
148
|
+
for (const rawLine of text.split('\n')) {
|
|
149
|
+
const match = lineWithoutBullet(rawLine).match(EXPLICIT_VERDICT_LINE);
|
|
150
|
+
if (match?.[1])
|
|
151
|
+
found = match[1].toUpperCase();
|
|
101
152
|
}
|
|
102
153
|
return found;
|
|
103
154
|
}
|
|
104
155
|
export function parseConsultVerdictFromText(text) {
|
|
105
|
-
const
|
|
156
|
+
const decision = firstHeaderDecision(text);
|
|
157
|
+
if (decision)
|
|
158
|
+
return decision;
|
|
159
|
+
// Verdict lines inside REASONING are model text, not the vote.
|
|
160
|
+
const { header, reasoning } = splitAtReasoning(text);
|
|
161
|
+
const scanTarget = reasoning === null ? text : header;
|
|
162
|
+
const explicit = lastExplicitVerdictLine(scanTarget);
|
|
106
163
|
if (explicit)
|
|
107
164
|
return explicit;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
165
|
+
if (!text.trim())
|
|
166
|
+
return null;
|
|
167
|
+
if (hasAmbiguousVerdictLine(text) || VERDICT_OPTION_ECHO.test(text))
|
|
168
|
+
return 'UNREVIEWED';
|
|
169
|
+
if (/\b(?:PASS|FAIL|CONDITIONAL|UNREVIEWED|SHIP|APPROVE|REJECT)\b/i.test(text)) {
|
|
170
|
+
return 'UNREVIEWED';
|
|
113
171
|
}
|
|
114
|
-
const normalized = text.toUpperCase();
|
|
115
|
-
if (/\bCONDITIONAL(\s+PASS)?\b/.test(normalized))
|
|
116
|
-
return 'CONDITIONAL';
|
|
117
|
-
if (/\b(?:PASS|SHIP|APPROVE)\b/.test(normalized))
|
|
118
|
-
return 'PASS';
|
|
119
|
-
if (/\b(?:FAIL|REJECT)\b/.test(normalized))
|
|
120
|
-
return 'FAIL';
|
|
121
172
|
return null;
|
|
122
173
|
}
|
|
123
174
|
export function extractTopRisksFromText(text, max = 5) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { frameworkLogger } from "../core/framework-logger.js";
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import {
|
|
5
|
-
import { getComprehensiveLogs, filterLogsByConfig, frameworkLogToParsedEntry, } from "./log-parser.js";
|
|
4
|
+
import { getComprehensiveLogs, filterLogsByConfig, frameworkLogToParsedEntry, activityLogPath, } from "./log-parser.js";
|
|
6
5
|
import { calculateTimeRange, calculateMetrics, calculatePeakActivity, calculateHealthScore, generateInsights, generateRecommendations, generateAlerts, } from "./metrics.js";
|
|
7
6
|
import { formatReport } from "./report-formatter.js";
|
|
8
7
|
export class FrameworkReportingSystem {
|
|
@@ -106,9 +105,7 @@ const report = await reportingSystem.generateCustomReport('${template.name}');
|
|
|
106
105
|
}
|
|
107
106
|
// Same activity.log path readCurrentLogFile rebuilds from size and mtime.
|
|
108
107
|
activityLogFile() {
|
|
109
|
-
|
|
110
|
-
const projectRoot = path.resolve(path.dirname(currentFilePath), "../../");
|
|
111
|
-
return path.join(projectRoot, "logs", "framework", "activity.log");
|
|
108
|
+
return activityLogPath();
|
|
112
109
|
}
|
|
113
110
|
currentLogStamp() {
|
|
114
111
|
try {
|