@mamdouh-aboammar/agentic-workflow 1.2.0
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/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +13 -0
- package/.skills.json +19 -0
- package/AGENTS.md +1344 -0
- package/CLAUDE.md +178 -0
- package/GEMINI.md +102 -0
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/SKILL.md +132 -0
- package/bin/agentic-hooks.sh +79 -0
- package/bin/cli.js +1060 -0
- package/core/__init__.py +52 -0
- package/core/ai_evaluator.py +117 -0
- package/core/autopilot_engine.py +368 -0
- package/core/clean_code_guard.py +188 -0
- package/core/engine_py/__init__.py +29 -0
- package/core/engine_py/agent_worker.py +136 -0
- package/core/engine_py/decider.py +150 -0
- package/core/engine_py/energy.py +45 -0
- package/core/engine_py/event_bus.py +63 -0
- package/core/engine_py/executor.py +186 -0
- package/core/engine_py/models.py +193 -0
- package/core/engine_py/queue.py +314 -0
- package/core/engine_py/runner.py +116 -0
- package/core/engine_py/system_workers.py +70 -0
- package/core/engine_py/toon_adapter.py +586 -0
- package/core/engine_py/verification_controller.py +208 -0
- package/core/engine_py/worker.py +167 -0
- package/core/engine_spec/event_schema.json +65 -0
- package/core/engine_spec/example_workflow.yaml +73 -0
- package/core/engine_spec/workflow_schema.json +127 -0
- package/core/hooks/__init__.py +29 -0
- package/core/hooks/adapters/__init__.py +25 -0
- package/core/hooks/adapters/claude_adapter.py +83 -0
- package/core/hooks/adapters/cli_agent_adapter.py +82 -0
- package/core/hooks/adapters/codex_adapter.py +78 -0
- package/core/hooks/adapters/cursor_adapter.py +73 -0
- package/core/hooks/adapters/gemini_adapter.py +93 -0
- package/core/hooks/adapters/homebrew_adapter.py +69 -0
- package/core/hooks/adapters/mcp_proxy.py +133 -0
- package/core/hooks/adapters/shell_adapter.py +65 -0
- package/core/hooks/dispatcher.py +118 -0
- package/core/hooks/policy_engine.py +375 -0
- package/core/hooks/session_end.py +141 -0
- package/core/hooks/types.py +147 -0
- package/core/integrations/__init__.py +28 -0
- package/core/integrations/installer.py +225 -0
- package/core/integrations/lifecycle_director.py +175 -0
- package/core/integrations/registry.py +105 -0
- package/core/multi_agent_system.py +164 -0
- package/core/skills_indexer.py +742 -0
- package/core/system/__init__.py +25 -0
- package/core/system/announcements.py +72 -0
- package/core/system/dependencies.py +69 -0
- package/core/system/doctor.py +171 -0
- package/core/system/health.py +144 -0
- package/core/system/installer.py +137 -0
- package/core/system/notifications.py +97 -0
- package/core/system/refresher.py +110 -0
- package/core/system/updater.py +167 -0
- package/core/system/version_tracker.py +65 -0
- package/docs/architecture_plan.md +7 -0
- package/docs/guides/failure-recovery.md +714 -0
- package/docs/implementation_summary.md +10 -0
- package/docs/protocols/autopilot-execution.md +148 -0
- package/docs/protocols/code-change-protocol.md +49 -0
- package/docs/protocols/context-preservation-detail.md +114 -0
- package/docs/protocols/quality-gates.md +110 -0
- package/docs/protocols/ulw-mode.md +60 -0
- package/docs/research_findings.md +10 -0
- package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
- package/install.sh +111 -0
- package/marketplace.json +37 -0
- package/package.json +81 -0
- package/skills/agentic-workflow/SKILL.md +132 -0
- package/skills/agentic-workflow/skill-spec.json +100 -0
- package/soul.md +445 -0
- package/src/engine_ts/decider.ts +186 -0
- package/src/engine_ts/event-bus.ts +57 -0
- package/src/engine_ts/executor.ts +262 -0
- package/src/engine_ts/index.ts +12 -0
- package/src/engine_ts/queue.ts +93 -0
- package/src/engine_ts/runner.ts +108 -0
- package/src/engine_ts/skills-indexer.ts +264 -0
- package/src/engine_ts/toon-adapter.ts +91 -0
- package/src/engine_ts/types.ts +134 -0
- package/src/engine_ts/verification-controller.ts +204 -0
- package/src/engine_ts/worker.ts +280 -0
- package/src/hooks/adapters/claude-adapter.ts +54 -0
- package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
- package/src/hooks/adapters/codex-adapter.ts +69 -0
- package/src/hooks/adapters/cursor-adapter.ts +60 -0
- package/src/hooks/adapters/gemini-adapter.ts +71 -0
- package/src/hooks/adapters/homebrew-adapter.ts +36 -0
- package/src/hooks/adapters/mcp-proxy.ts +66 -0
- package/src/hooks/adapters/shell-adapter.ts +42 -0
- package/src/hooks/dispatcher.ts +113 -0
- package/src/hooks/index.ts +16 -0
- package/src/hooks/policy-engine.ts +376 -0
- package/src/hooks/session-end.ts +125 -0
- package/src/hooks/types.ts +61 -0
- package/src/index.d.ts +34 -0
- package/src/index.ts +23 -0
- package/src/integrations/index.ts +7 -0
- package/src/integrations/installer.ts +208 -0
- package/src/integrations/lifecycle-director.ts +139 -0
- package/src/integrations/registry.ts +82 -0
- package/src/system/announcements.ts +143 -0
- package/src/system/dependencies.ts +176 -0
- package/src/system/doctor.ts +374 -0
- package/src/system/health.ts +270 -0
- package/src/system/index.ts +14 -0
- package/src/system/installer.ts +262 -0
- package/src/system/notifications.ts +180 -0
- package/src/system/refresher.ts +207 -0
- package/src/system/types.ts +268 -0
- package/src/system/updater.ts +219 -0
- package/src/system/version-tracker.ts +137 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skills-indexer.ts — TypeScript Agentic Skills Mesh Reader & Intent Resolver
|
|
3
|
+
*
|
|
4
|
+
* Provides runtime consumption of skills-index.json and skills-index.toon
|
|
5
|
+
* for the Bun/TypeScript Agentic Engine and coding agents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
export enum NodeType {
|
|
12
|
+
SKILL = "skill",
|
|
13
|
+
WORKFLOW = "workflow",
|
|
14
|
+
AGENT = "agent",
|
|
15
|
+
PLAYBOOK = "playbook",
|
|
16
|
+
RULE = "rule",
|
|
17
|
+
GUARD = "guard"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AgenticNode {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: NodeType;
|
|
24
|
+
path: string;
|
|
25
|
+
description: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
author?: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
triggers?: string[];
|
|
30
|
+
tools_required?: string[];
|
|
31
|
+
rules_and_guards?: string[];
|
|
32
|
+
edges?: string[];
|
|
33
|
+
source_dir?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SkillsIndexPayload {
|
|
37
|
+
version: string;
|
|
38
|
+
generated_at: string;
|
|
39
|
+
total_nodes: number;
|
|
40
|
+
stats: Record<string, number>;
|
|
41
|
+
nodes: AgenticNode[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class SkillsRegistry {
|
|
45
|
+
private nodes: Map<string, AgenticNode> = new Map();
|
|
46
|
+
private projectDir: string;
|
|
47
|
+
private jsonPath: string;
|
|
48
|
+
private toonPath: string;
|
|
49
|
+
|
|
50
|
+
constructor(projectDir: string = ".") {
|
|
51
|
+
this.projectDir = path.resolve(projectDir);
|
|
52
|
+
this.jsonPath = path.join(this.projectDir, "skills-index.json");
|
|
53
|
+
this.toonPath = path.join(this.projectDir, "skills-index.toon");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Loads skills from skills-index.json or skills-index.toon.
|
|
58
|
+
*/
|
|
59
|
+
public loadIndex(): boolean {
|
|
60
|
+
if (fs.existsSync(this.jsonPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const raw = fs.readFileSync(this.jsonPath, "utf-8");
|
|
63
|
+
const payload: SkillsIndexPayload = JSON.parse(raw);
|
|
64
|
+
this.nodes.clear();
|
|
65
|
+
for (const n of payload.nodes) {
|
|
66
|
+
this.nodes.set(n.id, n);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Fall back to TOON
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (fs.existsSync(this.toonPath)) {
|
|
75
|
+
try {
|
|
76
|
+
const raw = fs.readFileSync(this.toonPath, "utf-8");
|
|
77
|
+
const parsed = this.parseToon(raw);
|
|
78
|
+
this.nodes.clear();
|
|
79
|
+
for (const n of parsed) {
|
|
80
|
+
this.nodes.set(n.id, n);
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Parses TOON content into typed AgenticNode array.
|
|
93
|
+
*/
|
|
94
|
+
public parseToon(content: string): AgenticNode[] {
|
|
95
|
+
const nodes: AgenticNode[] = [];
|
|
96
|
+
let current: Partial<AgenticNode> | null = null;
|
|
97
|
+
|
|
98
|
+
const lines = content.split("\n");
|
|
99
|
+
for (const rawLine of lines) {
|
|
100
|
+
const line = rawLine.trim();
|
|
101
|
+
if (!line || line.startsWith("#")) continue;
|
|
102
|
+
|
|
103
|
+
if (line.startsWith("@node:")) {
|
|
104
|
+
if (current && current.id) {
|
|
105
|
+
nodes.push(current as AgenticNode);
|
|
106
|
+
}
|
|
107
|
+
current = {
|
|
108
|
+
id: "",
|
|
109
|
+
name: "",
|
|
110
|
+
type: NodeType.SKILL,
|
|
111
|
+
path: "",
|
|
112
|
+
description: "",
|
|
113
|
+
triggers: [],
|
|
114
|
+
tools_required: [],
|
|
115
|
+
rules_and_guards: [],
|
|
116
|
+
edges: []
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const match = line.match(/^@node:([^\s\[]+)(?:\s*\[(.*)\])?/);
|
|
120
|
+
if (match) {
|
|
121
|
+
current.id = match[1];
|
|
122
|
+
const attrs = match[2];
|
|
123
|
+
if (attrs) {
|
|
124
|
+
const parts = attrs.split(/,\s*(?=[a-zA-Z_]+:)/);
|
|
125
|
+
for (const p of parts) {
|
|
126
|
+
if (p.includes(":")) {
|
|
127
|
+
const [k, v] = p.split(":", 2);
|
|
128
|
+
const key = k.trim();
|
|
129
|
+
const val = v.trim().replace(/^["']|["']$/g, "");
|
|
130
|
+
if (key === "type") {
|
|
131
|
+
current.type = val as NodeType;
|
|
132
|
+
} else if (key === "name") {
|
|
133
|
+
current.name = val;
|
|
134
|
+
} else if (key === "tools") {
|
|
135
|
+
current.tools_required = val.split(",").map(x => x.trim()).filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else if (current) {
|
|
142
|
+
if (line.startsWith("path:")) {
|
|
143
|
+
current.path = line.substring(5).trim();
|
|
144
|
+
} else if (line.startsWith("summary:")) {
|
|
145
|
+
current.description = line.substring(8).trim();
|
|
146
|
+
} else if (line.startsWith("triggers:")) {
|
|
147
|
+
current.triggers = line.substring(9).split(",").map(x => x.trim()).filter(Boolean);
|
|
148
|
+
} else if (line.startsWith("links:")) {
|
|
149
|
+
current.edges = line.substring(6).split(",").map(x => x.trim()).filter(Boolean);
|
|
150
|
+
} else if (line.startsWith("rules:")) {
|
|
151
|
+
current.rules_and_guards = line.substring(6).split(",").map(x => x.trim()).filter(Boolean);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (current && current.id) {
|
|
157
|
+
nodes.push(current as AgenticNode);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return nodes;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public getNode(id: string): AgenticNode | undefined {
|
|
164
|
+
if (this.nodes.size === 0) this.loadIndex();
|
|
165
|
+
return this.nodes.get(id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public getAllNodes(): AgenticNode[] {
|
|
169
|
+
if (this.nodes.size === 0) this.loadIndex();
|
|
170
|
+
return Array.from(this.nodes.values());
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public search(query: string, limit: number = 10): AgenticNode[] {
|
|
174
|
+
if (this.nodes.size === 0) this.loadIndex();
|
|
175
|
+
const terms = query.toLowerCase().split(/[\s\-_]+/).filter(Boolean);
|
|
176
|
+
if (terms.length === 0) return Array.from(this.nodes.values()).slice(0, limit);
|
|
177
|
+
|
|
178
|
+
const scored: Array<{ score: number; node: AgenticNode }> = [];
|
|
179
|
+
for (const node of this.nodes.values()) {
|
|
180
|
+
let score = 0;
|
|
181
|
+
const haystack = `${node.id} ${node.name} ${node.description} ${(node.tags || []).join(' ')} ${(node.triggers || []).join(' ')}`.toLowerCase();
|
|
182
|
+
|
|
183
|
+
for (const t of terms) {
|
|
184
|
+
if (node.id.toLowerCase() === t) score += 20;
|
|
185
|
+
else if (node.name.toLowerCase().includes(t)) score += 10;
|
|
186
|
+
else if ((node.triggers || []).some(tr => tr.toLowerCase().includes(t))) score += 5;
|
|
187
|
+
else if (haystack.includes(t)) score += 2;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (score > 0) {
|
|
191
|
+
scored.push({ score, node });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
scored.sort((a, b) => b.score - a.score);
|
|
196
|
+
return scored.slice(0, limit).map(s => s.node);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public resolveForIntent(intent: string, limit: number = 5): AgenticNode[] {
|
|
200
|
+
const candidates = this.search(intent, limit * 2);
|
|
201
|
+
const selected: AgenticNode[] = [];
|
|
202
|
+
const seenIds = new Set<string>();
|
|
203
|
+
|
|
204
|
+
const intentLower = intent.toLowerCase();
|
|
205
|
+
const needsCodeGuard = /write|code|implement|refactor|fix|feature/.test(intentLower);
|
|
206
|
+
const needsTestGuard = /test|verify|qa|assert|spec/.test(intentLower);
|
|
207
|
+
|
|
208
|
+
if (needsCodeGuard) {
|
|
209
|
+
for (const gid of ["clean-code-guard", "autoreview"]) {
|
|
210
|
+
const node = this.getNode(gid);
|
|
211
|
+
if (node && !seenIds.has(gid)) {
|
|
212
|
+
selected.push(node);
|
|
213
|
+
seenIds.add(gid);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (needsTestGuard) {
|
|
219
|
+
for (const tid of ["test-guard", "test-driven-development", "fable-tdd"]) {
|
|
220
|
+
const node = this.getNode(tid);
|
|
221
|
+
if (node && !seenIds.has(tid)) {
|
|
222
|
+
selected.push(node);
|
|
223
|
+
seenIds.add(tid);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const c of candidates) {
|
|
229
|
+
if (!seenIds.has(c.id) && selected.length < limit) {
|
|
230
|
+
selected.push(c);
|
|
231
|
+
seenIds.add(c.id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return selected;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public getToonContext(nodeIds?: string[]): string {
|
|
239
|
+
if (this.nodes.size === 0) this.loadIndex();
|
|
240
|
+
const targetNodes = nodeIds
|
|
241
|
+
? nodeIds.map(id => this.nodes.get(id)).filter((n): n is AgenticNode => Boolean(n))
|
|
242
|
+
: Array.from(this.nodes.values());
|
|
243
|
+
|
|
244
|
+
const lines: string[] = [
|
|
245
|
+
"# AGENTIC SKILLS MESH (TOON CONTEXT)",
|
|
246
|
+
`# Total Active Nodes: ${targetNodes.length}`,
|
|
247
|
+
""
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
for (const n of targetNodes) {
|
|
251
|
+
const tools = n.tools_required?.join(",") || "none";
|
|
252
|
+
const triggers = n.triggers?.slice(0, 6).join(",") || "";
|
|
253
|
+
const links = n.edges?.join(",") || "";
|
|
254
|
+
lines.push(`@node:${n.id} [type:${n.type}, name:"${n.name}", tools:${tools}]`);
|
|
255
|
+
lines.push(`path:${n.path}`);
|
|
256
|
+
lines.push(`summary:${n.description}`);
|
|
257
|
+
if (triggers) lines.push(`triggers:${triggers}`);
|
|
258
|
+
if (links) lines.push(`links:${links}`);
|
|
259
|
+
lines.push("");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toon-adapter.ts — Official TOON v4.1 Integration for TypeScript / Bun
|
|
3
|
+
*
|
|
4
|
+
* Powered by @toon-format/toon (https://github.com/toon-format/toon)
|
|
5
|
+
* Enforces high-density, token-efficient serialization across all TS engine workers,
|
|
6
|
+
* inter-agent communications, and conversation history context injection.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { encode, decode } from "@toon-format/toon";
|
|
10
|
+
|
|
11
|
+
export interface ToonEncodeOptions {
|
|
12
|
+
delimiter?: "," | "\t" | "|";
|
|
13
|
+
indent?: number;
|
|
14
|
+
lengthMarker?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToonDecodeOptions {
|
|
18
|
+
indent?: number;
|
|
19
|
+
strict?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TokenSavingsStats {
|
|
23
|
+
jsonChars: number;
|
|
24
|
+
toonChars: number;
|
|
25
|
+
jsonEstimatedTokens: number;
|
|
26
|
+
toonEstimatedTokens: number;
|
|
27
|
+
savingsPercent: number;
|
|
28
|
+
bytesRatio: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ConversationTurn {
|
|
32
|
+
role: string;
|
|
33
|
+
content: string;
|
|
34
|
+
[key: string]: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Encodes arbitrary JavaScript/TypeScript object or array to official TOON string.
|
|
39
|
+
*/
|
|
40
|
+
export function encodeToon(data: any, options?: ToonEncodeOptions): string {
|
|
41
|
+
return encode(data, options as any);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Decodes official TOON formatted text back into JavaScript objects.
|
|
46
|
+
*/
|
|
47
|
+
export function decodeToon(content: string, options?: ToonDecodeOptions): any {
|
|
48
|
+
return decode(content, options as any);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encodes multi-turn agent conversation history into a dense TOON tabular block.
|
|
53
|
+
* Cuts context token consumption by ~40-60% compared to JSON.
|
|
54
|
+
*/
|
|
55
|
+
export function formatConversationTurns(turns: ConversationTurn[]): string {
|
|
56
|
+
const normalized = turns.map((t, idx) => ({
|
|
57
|
+
idx: idx + 1,
|
|
58
|
+
role: t.role || "assistant",
|
|
59
|
+
content: (t.content || "").replace(/\r?\n/g, " ").trim()
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return encodeToon({ dialogue: normalized });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calculates token efficiency and savings percentage vs standard formatted JSON.
|
|
67
|
+
*/
|
|
68
|
+
export function calculateTokenSavings(data: any): TokenSavingsStats {
|
|
69
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
70
|
+
const toonStr = encodeToon(data);
|
|
71
|
+
|
|
72
|
+
const jsonChars = jsonStr.length;
|
|
73
|
+
const toonChars = toonStr.length;
|
|
74
|
+
|
|
75
|
+
const jsonTokens = Math.max(1, Math.floor(jsonChars / 4));
|
|
76
|
+
const toonTokens = Math.max(1, Math.floor(toonChars / 4));
|
|
77
|
+
|
|
78
|
+
const savingsPercent = Math.max(0, Math.round((1 - toonTokens / jsonTokens) * 100));
|
|
79
|
+
const bytesRatio = Number((toonChars / Math.max(1, jsonChars)).toFixed(2));
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
jsonChars,
|
|
83
|
+
toonChars,
|
|
84
|
+
jsonEstimatedTokens: jsonTokens,
|
|
85
|
+
toonEstimatedTokens: toonTokens,
|
|
86
|
+
savingsPercent,
|
|
87
|
+
bytesRatio
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { encode, decode };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts — TypeScript Interfaces and Enums for Agentic Engine.
|
|
3
|
+
*
|
|
4
|
+
* Provides 100% schema parity with Python engine and shared JSON specs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export enum TaskStatus {
|
|
8
|
+
SCHEDULED = "SCHEDULED",
|
|
9
|
+
POLLED = "POLLED",
|
|
10
|
+
IN_PROGRESS = "IN_PROGRESS",
|
|
11
|
+
GATE_EVALUATING = "GATE_EVALUATING",
|
|
12
|
+
DIAGNOSING = "DIAGNOSING",
|
|
13
|
+
COMPLETED = "COMPLETED",
|
|
14
|
+
FAILED = "FAILED",
|
|
15
|
+
TIMED_OUT = "TIMED_OUT",
|
|
16
|
+
SKIPPED = "SKIPPED",
|
|
17
|
+
CANCELLED = "CANCELLED"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum WorkflowStatus {
|
|
21
|
+
RUNNING = "RUNNING",
|
|
22
|
+
PAUSED = "PAUSED",
|
|
23
|
+
COMPLETED = "COMPLETED",
|
|
24
|
+
FAILED = "FAILED",
|
|
25
|
+
CANCELLED = "CANCELLED"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export enum BackoffType {
|
|
29
|
+
FIXED = "FIXED",
|
|
30
|
+
LINEAR = "LINEAR",
|
|
31
|
+
EXPONENTIAL = "EXPONENTIAL"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export enum AgentRole {
|
|
35
|
+
ORCHESTRATOR = "orchestrator",
|
|
36
|
+
RESEARCHER = "researcher",
|
|
37
|
+
ARCHITECT = "architect",
|
|
38
|
+
ENGINEER = "engineer",
|
|
39
|
+
REVIEWER = "reviewer",
|
|
40
|
+
FACT_CHECKER = "fact_checker",
|
|
41
|
+
CLEAN_CODE_GUARD = "clean_code_guard",
|
|
42
|
+
RECOVERY = "recovery"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RetryPolicy {
|
|
46
|
+
max_retries: number;
|
|
47
|
+
delay_seconds: number;
|
|
48
|
+
backoff_rate: BackoffType;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CircuitBreakerConfig {
|
|
52
|
+
failure_threshold: number;
|
|
53
|
+
cooldown_seconds: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TaskDefinition {
|
|
57
|
+
id: string;
|
|
58
|
+
type: string; // system.code, system.wait, agent.task, agent.human, agent.review
|
|
59
|
+
name?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
role?: AgentRole;
|
|
62
|
+
deliverable_path?: string;
|
|
63
|
+
criteria?: string[];
|
|
64
|
+
input_parameters?: Record<string, any>;
|
|
65
|
+
retry_policy?: RetryPolicy;
|
|
66
|
+
timeout_seconds?: number;
|
|
67
|
+
circuit_breaker?: CircuitBreakerConfig;
|
|
68
|
+
compensation_task?: string;
|
|
69
|
+
branches?: Record<string, any>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface StageDefinition {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
stage_type?: "research" | "planning" | "implementation" | "verification" | "custom";
|
|
76
|
+
tasks: TaskDefinition[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface WorkflowDefinition {
|
|
80
|
+
name: string;
|
|
81
|
+
version: string;
|
|
82
|
+
description?: string;
|
|
83
|
+
stages: StageDefinition[];
|
|
84
|
+
input_parameters?: Record<string, any>;
|
|
85
|
+
timeout_seconds?: number;
|
|
86
|
+
failure_workflow?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TaskInstance {
|
|
90
|
+
task_id: string;
|
|
91
|
+
workflow_id: string;
|
|
92
|
+
stage_id: string;
|
|
93
|
+
task_def: TaskDefinition;
|
|
94
|
+
status: TaskStatus;
|
|
95
|
+
attempt: number;
|
|
96
|
+
input_data: Record<string, any>;
|
|
97
|
+
output_data: Record<string, any>;
|
|
98
|
+
worker_id?: string | null;
|
|
99
|
+
scheduled_at: number;
|
|
100
|
+
started_at?: number | null;
|
|
101
|
+
completed_at?: number | null;
|
|
102
|
+
lease_expires_at?: number | null;
|
|
103
|
+
pacs_score?: number | null;
|
|
104
|
+
gate_verdict?: string | null;
|
|
105
|
+
error_message?: string | null;
|
|
106
|
+
trace_id: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface WorkflowInstance {
|
|
110
|
+
workflow_id: string;
|
|
111
|
+
workflow_def: WorkflowDefinition;
|
|
112
|
+
trace_id: string;
|
|
113
|
+
status: WorkflowStatus;
|
|
114
|
+
current_stage_index: number;
|
|
115
|
+
tasks: Record<string, TaskInstance>;
|
|
116
|
+
variables: Record<string, any>;
|
|
117
|
+
outputs: Record<string, any>;
|
|
118
|
+
started_at: number;
|
|
119
|
+
completed_at?: number | null;
|
|
120
|
+
autopilot_enabled: boolean;
|
|
121
|
+
error_message?: string | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface EngineEvent {
|
|
125
|
+
event_id: string;
|
|
126
|
+
event_type: string;
|
|
127
|
+
timestamp: number;
|
|
128
|
+
trace_id: string;
|
|
129
|
+
workflow_id: string;
|
|
130
|
+
stage_id?: string | null;
|
|
131
|
+
task_id?: string | null;
|
|
132
|
+
worker_id?: string | null;
|
|
133
|
+
payload?: Record<string, any>;
|
|
134
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verification-controller.ts — 4-Layer Verification & Abductive Diagnosis (TypeScript).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { TaskInstance } from "./types.js";
|
|
8
|
+
|
|
9
|
+
export interface GateResult {
|
|
10
|
+
passed: boolean;
|
|
11
|
+
l0_passed: boolean;
|
|
12
|
+
l1_passed: boolean;
|
|
13
|
+
l15_pacs_score: number;
|
|
14
|
+
l2_verdict: string;
|
|
15
|
+
error_message?: string | null;
|
|
16
|
+
diagnosis_report?: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class VerificationController {
|
|
20
|
+
private projectDir: string;
|
|
21
|
+
|
|
22
|
+
constructor(projectDir: string = ".") {
|
|
23
|
+
this.projectDir = path.resolve(projectDir);
|
|
24
|
+
this.ensureLogDirs();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private ensureLogDirs(): void {
|
|
28
|
+
for (const d of ["pacs-logs", "review-logs", "diagnosis-logs", "verification-logs"]) {
|
|
29
|
+
fs.mkdirSync(path.join(this.projectDir, d), { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public evaluateL0AntiSkip(task: TaskInstance): { passed: boolean; message: string } {
|
|
34
|
+
const deliverableRel = task.task_def.deliverable_path;
|
|
35
|
+
if (!deliverableRel) {
|
|
36
|
+
return { passed: true, message: "No deliverable path specified; L0 passed." };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const fullPath = path.join(this.projectDir, deliverableRel);
|
|
40
|
+
if (!fs.existsSync(fullPath)) {
|
|
41
|
+
return { passed: false, message: `L0 Failed: Deliverable '${deliverableRel}' not found on disk.` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const stats = fs.statSync(fullPath);
|
|
45
|
+
if (stats.size < 100) {
|
|
46
|
+
return { passed: false, message: `L0 Failed: Deliverable '${deliverableRel}' is only ${stats.size} bytes (<100 required).` };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { passed: true, message: `L0 Passed: Deliverable verified (${stats.size} bytes).` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public evaluateL1Functional(task: TaskInstance): { passed: boolean; message: string } {
|
|
53
|
+
const deliverableRel = task.task_def.deliverable_path;
|
|
54
|
+
const criteria = task.task_def.criteria;
|
|
55
|
+
if (!deliverableRel || !criteria || criteria.length === 0) {
|
|
56
|
+
return { passed: true, message: "No criteria defined." };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const fullPath = path.join(this.projectDir, deliverableRel);
|
|
60
|
+
if (!fs.existsSync(fullPath)) {
|
|
61
|
+
return { passed: false, message: "L1 Failed: Deliverable file missing." };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const content = fs.readFileSync(fullPath, "utf-8").toLowerCase();
|
|
65
|
+
const missing: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const crit of criteria) {
|
|
68
|
+
const critLower = crit.toLowerCase();
|
|
69
|
+
if (!content.includes(critLower) && !critLower.split(" ").some(w => w.length > 4 && content.includes(w))) {
|
|
70
|
+
missing.push(crit);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (missing.length > 0) {
|
|
75
|
+
return { passed: false, message: `L1 Failed: Missing criteria: ${missing.join(", ")}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { passed: true, message: `L1 Passed: All ${criteria.length} criteria satisfied.` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public evaluateL15Pacs(task: TaskInstance): { score: number; colorZone: string } {
|
|
82
|
+
const deliverableRel = task.task_def.deliverable_path;
|
|
83
|
+
let fScore = 92;
|
|
84
|
+
let cScore = 90;
|
|
85
|
+
let lScore = 88;
|
|
86
|
+
|
|
87
|
+
if (deliverableRel) {
|
|
88
|
+
const fullPath = path.join(this.projectDir, deliverableRel);
|
|
89
|
+
if (fs.existsSync(fullPath)) {
|
|
90
|
+
const stats = fs.statSync(fullPath);
|
|
91
|
+
if (stats.size < 200) cScore = 65;
|
|
92
|
+
} else {
|
|
93
|
+
fScore = 0;
|
|
94
|
+
cScore = 0;
|
|
95
|
+
lScore = 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const pacsScore = Math.min(fScore, cScore, lScore);
|
|
100
|
+
const colorZone = pacsScore >= 70 ? "GREEN" : pacsScore >= 50 ? "YELLOW" : "RED";
|
|
101
|
+
|
|
102
|
+
const logFile = path.join(this.projectDir, "pacs-logs", `step-${task.task_id}-pacs.md`);
|
|
103
|
+
try {
|
|
104
|
+
fs.writeFileSync(
|
|
105
|
+
logFile,
|
|
106
|
+
`# pACS Calibration Log: ${task.task_id}\n\n` +
|
|
107
|
+
`- Faithfulness: ${fScore}/100\n` +
|
|
108
|
+
`- Completeness: ${cScore}/100\n` +
|
|
109
|
+
`- Logic: ${lScore}/100\n\n` +
|
|
110
|
+
`**pACS = min(F, C, L) = ${pacsScore} (${colorZone} Zone)**\n\n` +
|
|
111
|
+
`## Pre-mortem Analysis\n- Evaluated hallucinations, timeouts, and depth.\n`
|
|
112
|
+
);
|
|
113
|
+
} catch (_) {}
|
|
114
|
+
|
|
115
|
+
return { score: pacsScore, colorZone };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public evaluateL2Review(task: TaskInstance): { verdict: string; message: string } {
|
|
119
|
+
const verdict = "PASS";
|
|
120
|
+
const logFile = path.join(this.projectDir, "review-logs", `step-${task.task_id}-review.md`);
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(
|
|
123
|
+
logFile,
|
|
124
|
+
`# Adversarial Review: ${task.task_id}\n\n` +
|
|
125
|
+
`- Deliverable: \`${task.task_def.deliverable_path}\`\n` +
|
|
126
|
+
`- Verdict: ${verdict}\n` +
|
|
127
|
+
`- Fact-Checker Verification: Clean.\n`
|
|
128
|
+
);
|
|
129
|
+
} catch (_) {}
|
|
130
|
+
|
|
131
|
+
return { verdict, message: `L2 Review verdict: ${verdict}` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public runAbductiveDiagnosis(task: TaskInstance, failureReason: string): string {
|
|
135
|
+
const logFile = path.join(this.projectDir, "diagnosis-logs", `step-${task.task_id}-diagnosis.md`);
|
|
136
|
+
const report = [
|
|
137
|
+
`# Abductive Diagnosis Report: Task ${task.task_id}`,
|
|
138
|
+
`- Timestamp: ${new Date().toISOString()}`,
|
|
139
|
+
`- Attempt: ${task.attempt}`,
|
|
140
|
+
`- Failure Reason: ${failureReason}`,
|
|
141
|
+
"\n## Step A: Observable Evidence",
|
|
142
|
+
`- Deliverable: \`${task.task_def.deliverable_path}\``,
|
|
143
|
+
"\n## Step B: Multi-Hypothesis Root Cause",
|
|
144
|
+
"- **H1 (Specification Mismatch)**: Criteria keyword missed in text flush.",
|
|
145
|
+
"- **H2 (Worker Crash)**: Process killed before buffer write.",
|
|
146
|
+
"\n## Step C: Recommended Remediation",
|
|
147
|
+
"- Re-execute task ensuring explicit criteria markers."
|
|
148
|
+
].join("\n");
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
fs.writeFileSync(logFile, report);
|
|
152
|
+
} catch (_) {}
|
|
153
|
+
|
|
154
|
+
return report;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public evaluateAllGates(task: TaskInstance): GateResult {
|
|
158
|
+
const l0 = this.evaluateL0AntiSkip(task);
|
|
159
|
+
if (!l0.passed) {
|
|
160
|
+
const diag = this.runAbductiveDiagnosis(task, l0.message);
|
|
161
|
+
return {
|
|
162
|
+
passed: false, l0_passed: false, l1_passed: false,
|
|
163
|
+
l15_pacs_score: 0, l2_verdict: "FAIL",
|
|
164
|
+
error_message: l0.message, diagnosis_report: diag
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const l1 = this.evaluateL1Functional(task);
|
|
169
|
+
if (!l1.passed) {
|
|
170
|
+
const diag = this.runAbductiveDiagnosis(task, l1.message);
|
|
171
|
+
return {
|
|
172
|
+
passed: false, l0_passed: true, l1_passed: false,
|
|
173
|
+
l15_pacs_score: 40, l2_verdict: "FAIL",
|
|
174
|
+
error_message: l1.message, diagnosis_report: diag
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const { score, colorZone } = this.evaluateL15Pacs(task);
|
|
179
|
+
if (score < 70) {
|
|
180
|
+
const msg = `L1.5 pACS Score ${score} in ${colorZone} Zone (<70)`;
|
|
181
|
+
const diag = this.runAbductiveDiagnosis(task, msg);
|
|
182
|
+
return {
|
|
183
|
+
passed: false, l0_passed: true, l1_passed: true,
|
|
184
|
+
l15_pacs_score: score, l2_verdict: "REWORK",
|
|
185
|
+
error_message: msg, diagnosis_report: diag
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const l2 = this.evaluateL2Review(task);
|
|
190
|
+
if (l2.verdict !== "PASS") {
|
|
191
|
+
const diag = this.runAbductiveDiagnosis(task, l2.message);
|
|
192
|
+
return {
|
|
193
|
+
passed: false, l0_passed: true, l1_passed: true,
|
|
194
|
+
l15_pacs_score: score, l2_verdict: l2.verdict,
|
|
195
|
+
error_message: l2.message, diagnosis_report: diag
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
passed: true, l0_passed: true, l1_passed: true,
|
|
201
|
+
l15_pacs_score: score, l2_verdict: l2.verdict
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|