@kognai/orchestrator-core 0.1.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/README.md +44 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +175 -0
- package/dist/lib/aar-middleware.d.ts +6 -0
- package/dist/lib/aar-middleware.js +70 -0
- package/dist/lib/aar-types.d.ts +34 -0
- package/dist/lib/aar-types.js +4 -0
- package/dist/lib/acp-engine.d.ts +68 -0
- package/dist/lib/acp-engine.js +123 -0
- package/dist/lib/acp.d.ts +61 -0
- package/dist/lib/acp.js +425 -0
- package/dist/lib/agent-registry.d.ts +50 -0
- package/dist/lib/agent-registry.js +137 -0
- package/dist/lib/anthropic-direct.d.ts +27 -0
- package/dist/lib/anthropic-direct.js +109 -0
- package/dist/lib/asmr-extractor.d.ts +40 -0
- package/dist/lib/asmr-extractor.js +151 -0
- package/dist/lib/asmr-retrieval.d.ts +76 -0
- package/dist/lib/asmr-retrieval.js +311 -0
- package/dist/lib/asmr.d.ts +8 -0
- package/dist/lib/asmr.js +24 -0
- package/dist/lib/brainx-client.d.ts +72 -0
- package/dist/lib/brainx-client.js +200 -0
- package/dist/lib/brainx-embed.d.ts +14 -0
- package/dist/lib/brainx-embed.js +139 -0
- package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
- package/dist/lib/brainx-swarm-bridge.js +242 -0
- package/dist/lib/byterover-client.d.ts +19 -0
- package/dist/lib/byterover-client.js +59 -0
- package/dist/lib/ceo-wallet.d.ts +37 -0
- package/dist/lib/ceo-wallet.js +176 -0
- package/dist/lib/chomsky-gate.d.ts +24 -0
- package/dist/lib/chomsky-gate.js +178 -0
- package/dist/lib/chomsky-runner.d.ts +29 -0
- package/dist/lib/chomsky-runner.js +157 -0
- package/dist/lib/citizen-score-contract.d.ts +72 -0
- package/dist/lib/citizen-score-contract.js +16 -0
- package/dist/lib/citizen-score-registry.d.ts +25 -0
- package/dist/lib/citizen-score-registry.js +65 -0
- package/dist/lib/citizenship.d.ts +103 -0
- package/dist/lib/citizenship.js +272 -0
- package/dist/lib/clawrouter-client.d.ts +37 -0
- package/dist/lib/clawrouter-client.js +148 -0
- package/dist/lib/code-asset-crystalliser.d.ts +41 -0
- package/dist/lib/code-asset-crystalliser.js +181 -0
- package/dist/lib/code-failure-logger.d.ts +27 -0
- package/dist/lib/code-failure-logger.js +42 -0
- package/dist/lib/cto-approval-gate.d.ts +45 -0
- package/dist/lib/cto-approval-gate.js +478 -0
- package/dist/lib/cto-gate-types.d.ts +28 -0
- package/dist/lib/cto-gate-types.js +8 -0
- package/dist/lib/decomposer-feedback.d.ts +54 -0
- package/dist/lib/decomposer-feedback.js +115 -0
- package/dist/lib/emotional-safety-gate.d.ts +48 -0
- package/dist/lib/emotional-safety-gate.js +97 -0
- package/dist/lib/engine-paths.d.ts +13 -0
- package/dist/lib/engine-paths.js +32 -0
- package/dist/lib/event-bus-listener.d.ts +8 -0
- package/dist/lib/event-bus-listener.js +144 -0
- package/dist/lib/event-bus-publisher.d.ts +25 -0
- package/dist/lib/event-bus-publisher.js +188 -0
- package/dist/lib/event-bus-types.d.ts +73 -0
- package/dist/lib/event-bus-types.js +23 -0
- package/dist/lib/failure-library.d.ts +178 -0
- package/dist/lib/failure-library.js +349 -0
- package/dist/lib/ksl/error-log.d.ts +28 -0
- package/dist/lib/ksl/error-log.js +43 -0
- package/dist/lib/ksl/index.d.ts +9 -0
- package/dist/lib/ksl/index.js +25 -0
- package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
- package/dist/lib/ksl/orchestrator-tap.js +85 -0
- package/dist/lib/ksl/record-writer.d.ts +46 -0
- package/dist/lib/ksl/record-writer.js +45 -0
- package/dist/lib/llm-cost-table.d.ts +36 -0
- package/dist/lib/llm-cost-table.js +90 -0
- package/dist/lib/local-model-router.d.ts +27 -0
- package/dist/lib/local-model-router.js +61 -0
- package/dist/lib/mc-client.d.ts +51 -0
- package/dist/lib/mc-client.js +249 -0
- package/dist/lib/model-router-contract.d.ts +91 -0
- package/dist/lib/model-router-contract.js +19 -0
- package/dist/lib/model-router-registry.d.ts +24 -0
- package/dist/lib/model-router-registry.js +52 -0
- package/dist/lib/model-router.d.ts +20 -0
- package/dist/lib/model-router.js +79 -0
- package/dist/lib/monotask-state-machine.d.ts +19 -0
- package/dist/lib/monotask-state-machine.js +131 -0
- package/dist/lib/neutral-prompt-checker.d.ts +22 -0
- package/dist/lib/neutral-prompt-checker.js +130 -0
- package/dist/lib/notion-direct.d.ts +92 -0
- package/dist/lib/notion-direct.js +381 -0
- package/dist/lib/ollama-client.d.ts +37 -0
- package/dist/lib/ollama-client.js +158 -0
- package/dist/lib/omel/credential-vault.d.ts +57 -0
- package/dist/lib/omel/credential-vault.js +324 -0
- package/dist/lib/omel/human-brake.d.ts +32 -0
- package/dist/lib/omel/human-brake.js +289 -0
- package/dist/lib/omel/index.d.ts +10 -0
- package/dist/lib/omel/index.js +26 -0
- package/dist/lib/omel/phantom-workspace.d.ts +31 -0
- package/dist/lib/omel/phantom-workspace.js +256 -0
- package/dist/lib/omel/wipe-witness.d.ts +75 -0
- package/dist/lib/omel/wipe-witness.js +398 -0
- package/dist/lib/orchestrate-engine.d.ts +25 -0
- package/dist/lib/orchestrate-engine.js +4436 -0
- package/dist/lib/perm-judge.d.ts +46 -0
- package/dist/lib/perm-judge.js +173 -0
- package/dist/lib/plumber/conformance.d.ts +54 -0
- package/dist/lib/plumber/conformance.js +121 -0
- package/dist/lib/plumber/index.d.ts +9 -0
- package/dist/lib/plumber/index.js +25 -0
- package/dist/lib/plumber/observer.d.ts +52 -0
- package/dist/lib/plumber/observer.js +180 -0
- package/dist/lib/plumber/types.d.ts +78 -0
- package/dist/lib/plumber/types.js +29 -0
- package/dist/lib/research-impl-gate.d.ts +16 -0
- package/dist/lib/research-impl-gate.js +105 -0
- package/dist/lib/sherlock-memory.d.ts +29 -0
- package/dist/lib/sherlock-memory.js +105 -0
- package/dist/lib/skill-crystalliser.d.ts +44 -0
- package/dist/lib/skill-crystalliser.js +60 -0
- package/dist/lib/sprint-runner-engine.d.ts +27 -0
- package/dist/lib/sprint-runner-engine.js +1042 -0
- package/dist/lib/sprint-state.d.ts +71 -0
- package/dist/lib/sprint-state.js +202 -0
- package/dist/lib/stuck-handler.d.ts +17 -0
- package/dist/lib/stuck-handler.js +249 -0
- package/dist/lib/task-contract-checker.d.ts +17 -0
- package/dist/lib/task-contract-checker.js +29 -0
- package/dist/lib/task-router/index.d.ts +17 -0
- package/dist/lib/task-router/index.js +52 -0
- package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
- package/dist/lib/task-router/router/generate-execution-id.js +24 -0
- package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
- package/dist/lib/task-router/router/resolve-route.js +49 -0
- package/dist/lib/task-router/types.d.ts +79 -0
- package/dist/lib/task-router/types.js +39 -0
- package/dist/lib/token-budget-validator.d.ts +44 -0
- package/dist/lib/token-budget-validator.js +84 -0
- package/dist/lib/trust-score-updater.d.ts +30 -0
- package/dist/lib/trust-score-updater.js +107 -0
- package/dist/lib/wallet-state.d.ts +26 -0
- package/dist/lib/wallet-state.js +85 -0
- package/package.json +27 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* model-router-registry.ts — TICKET-215 Wave C: the injection point for the
|
|
4
|
+
* model-routing seam (see model-router-contract.ts).
|
|
5
|
+
*
|
|
6
|
+
* Products wire their router at startup:
|
|
7
|
+
* - Kognai: setModelRouter(<viem-backed ClawRouter v2 adapter>)
|
|
8
|
+
* - others: inject a local/product-specific ModelRouter, or leave the
|
|
9
|
+
* fail-loud default in place until one is provided.
|
|
10
|
+
*
|
|
11
|
+
* The default is intentionally NOT a silent no-op: a routeCall with no router
|
|
12
|
+
* configured throws a clear, actionable error rather than returning empty
|
|
13
|
+
* completions. Replace with a real local-only default when one exists.
|
|
14
|
+
*
|
|
15
|
+
* Per-agent gating note: an injected router MAY consult the ACP capability
|
|
16
|
+
* registry (see ./acp) before performing a paid/on-chain call, so that only
|
|
17
|
+
* capability-bearing agents can spend — everyone else falls back to local.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.setModelRouter = setModelRouter;
|
|
21
|
+
exports.getModelRouter = getModelRouter;
|
|
22
|
+
exports.isModelRouterConfigured = isModelRouterConfigured;
|
|
23
|
+
const NOT_CONFIGURED = 'No ModelRouter registered. Call setModelRouter(...) at startup ' +
|
|
24
|
+
'(Kognai injects its viem-backed ClawRouter; other products inject their own ' +
|
|
25
|
+
'or a local router). See @kognai/orchestrator-core model-router-contract.ts.';
|
|
26
|
+
class UnconfiguredModelRouter {
|
|
27
|
+
routeCall(_req) {
|
|
28
|
+
throw new Error(NOT_CONFIGURED);
|
|
29
|
+
}
|
|
30
|
+
callLLM(_prompt, _opts) {
|
|
31
|
+
throw new Error(NOT_CONFIGURED);
|
|
32
|
+
}
|
|
33
|
+
healthCheck() {
|
|
34
|
+
throw new Error(NOT_CONFIGURED);
|
|
35
|
+
}
|
|
36
|
+
getDailyCostDigest() {
|
|
37
|
+
throw new Error(NOT_CONFIGURED);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let _router = new UnconfiguredModelRouter();
|
|
41
|
+
/** Inject the concrete router. Call once at process startup, before routing. */
|
|
42
|
+
function setModelRouter(router) {
|
|
43
|
+
_router = router;
|
|
44
|
+
}
|
|
45
|
+
/** Resolve the active router. The orchestrator routes every LLM call through this. */
|
|
46
|
+
function getModelRouter() {
|
|
47
|
+
return _router;
|
|
48
|
+
}
|
|
49
|
+
/** True once a real router has been injected (i.e. not the fail-loud default). */
|
|
50
|
+
function isModelRouterConfigured() {
|
|
51
|
+
return !(_router instanceof UnconfiguredModelRouter);
|
|
52
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-router.ts — Expertise routing matrix for ClawRouter model selection
|
|
3
|
+
*
|
|
4
|
+
* Maps task types to specialist models. Includes 3 Kognai-specific types
|
|
5
|
+
* beyond the Invoica base (refactor-complex, agent-framework, codebase-scan).
|
|
6
|
+
*/
|
|
7
|
+
export type TaskType = 'code' | 'reason' | 'lang' | 'util' | 'audit' | 'content' | 'data' | 'refactor-complex' | 'agent-framework' | 'codebase-scan';
|
|
8
|
+
interface ModelRoute {
|
|
9
|
+
primary: string;
|
|
10
|
+
fallback: string;
|
|
11
|
+
}
|
|
12
|
+
declare const EXPERTISE_MODELS: Record<TaskType, ModelRoute>;
|
|
13
|
+
export declare function classifyTask(prompt: string): TaskType;
|
|
14
|
+
export declare function selectModel(prompt: string, requestedModel?: string): {
|
|
15
|
+
model: string;
|
|
16
|
+
taskType: TaskType;
|
|
17
|
+
autoClassified: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function getFallbackModel(taskType: TaskType): string;
|
|
20
|
+
export { EXPERTISE_MODELS };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* model-router.ts — Expertise routing matrix for ClawRouter model selection
|
|
4
|
+
*
|
|
5
|
+
* Maps task types to specialist models. Includes 3 Kognai-specific types
|
|
6
|
+
* beyond the Invoica base (refactor-complex, agent-framework, codebase-scan).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.EXPERTISE_MODELS = void 0;
|
|
10
|
+
exports.classifyTask = classifyTask;
|
|
11
|
+
exports.selectModel = selectModel;
|
|
12
|
+
exports.getFallbackModel = getFallbackModel;
|
|
13
|
+
const EXPERTISE_MODELS = {
|
|
14
|
+
// Base types (same as Invoica)
|
|
15
|
+
code: { primary: 'deepseek/deepseek-chat', fallback: 'anthropic/claude-haiku-4.5' },
|
|
16
|
+
reason: { primary: 'deepseek/deepseek-reasoner', fallback: 'anthropic/claude-sonnet-4.6' },
|
|
17
|
+
lang: { primary: 'anthropic/claude-haiku-4.5', fallback: 'google/gemini-2.5-flash' },
|
|
18
|
+
util: { primary: 'google/gemini-2.5-flash-lite', fallback: 'anthropic/claude-haiku-4.5' },
|
|
19
|
+
audit: { primary: 'anthropic/claude-sonnet-4.6', fallback: 'deepseek/deepseek-chat' },
|
|
20
|
+
content: { primary: 'anthropic/claude-haiku-4.5', fallback: 'google/gemini-2.5-flash' },
|
|
21
|
+
data: { primary: 'deepseek/deepseek-chat', fallback: 'anthropic/claude-haiku-4.5' },
|
|
22
|
+
// Kognai-specific extensions
|
|
23
|
+
'refactor-complex': { primary: 'anthropic/claude-sonnet-4.6', fallback: 'deepseek/deepseek-reasoner' },
|
|
24
|
+
'agent-framework': { primary: 'deepseek/deepseek-chat', fallback: 'anthropic/claude-haiku-4.5' },
|
|
25
|
+
'codebase-scan': { primary: 'google/gemini-2.5-flash', fallback: 'deepseek/deepseek-chat' },
|
|
26
|
+
};
|
|
27
|
+
exports.EXPERTISE_MODELS = EXPERTISE_MODELS;
|
|
28
|
+
// ── Legacy Aliases ─────────────────────────────────────────────────────────────
|
|
29
|
+
const MODEL_ALIASES = {
|
|
30
|
+
'MiniMax-M2.5': 'minimax/minimax-m2.5',
|
|
31
|
+
'minimax-m2.5': 'minimax/minimax-m2.5',
|
|
32
|
+
'minimax-m2.5-lightning': 'minimax/minimax-m2.5',
|
|
33
|
+
'minimax': 'minimax/minimax-m2.5',
|
|
34
|
+
'coding': 'deepseek/deepseek-chat',
|
|
35
|
+
'claude-haiku-4-5': 'anthropic/claude-haiku-4.5',
|
|
36
|
+
'claude-haiku-4.5': 'anthropic/claude-haiku-4.5',
|
|
37
|
+
'claude-sonnet-4': 'anthropic/claude-sonnet-4.6',
|
|
38
|
+
'claude-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
|
|
39
|
+
'claude-3-haiku-20240307': 'anthropic/claude-haiku-4.5',
|
|
40
|
+
'claude-3-5-sonnet-20241022': 'anthropic/claude-sonnet-4.6',
|
|
41
|
+
'claude-sonnet-4-20250514': 'anthropic/claude-sonnet-4.6',
|
|
42
|
+
};
|
|
43
|
+
// ── Task Classification ────────────────────────────────────────────────────────
|
|
44
|
+
const TASK_PATTERNS = [
|
|
45
|
+
{ type: 'refactor-complex', keywords: /\b(refactor|restructure|migration|redesign|rewrite|overhaul)\b/i },
|
|
46
|
+
{ type: 'agent-framework', keywords: /\b(agent|swarm|orchestrat|pipeline|workflow|framework\s+\w+)\b/i },
|
|
47
|
+
{ type: 'codebase-scan', keywords: /\b(scan|codebase|search|index|inventory|dependency\s+audit|grep)\b/i },
|
|
48
|
+
{ type: 'code', keywords: /\b(code|function|debug|implement|typescript|javascript|python|fix\s+bug|compile|syntax|class\s+\w+|import\s+|async\s+function|interface\s+\w+)\b/i },
|
|
49
|
+
{ type: 'audit', keywords: /\b(security|audit|review|compliance|vulnerability|penetration|cve|owasp|exploit|threat|risk\s+assess)\b/i },
|
|
50
|
+
{ type: 'data', keywords: /\b(query|sql|database|aggregate|join|select\s+|group\s+by|csv|dataframe|pandas|analytics|metrics)\b/i },
|
|
51
|
+
{ type: 'lang', keywords: /\b(translate|translation|french|arabic|spanish|german|chinese|japanese|locali[sz]e|multilingual|i18n)\b/i },
|
|
52
|
+
{ type: 'reason', keywords: /\b(why|explain|analy[sz]e|tradeoff|compare|evaluate|pros\s+and\s+cons|reasoning|think\s+through)\b/i },
|
|
53
|
+
{ type: 'content', keywords: /\b(write|draft|compose|blog|caption|essay|article|tweet|post|newsletter|marketing|creative\s+writ)\b/i },
|
|
54
|
+
{ type: 'util', keywords: /\b(classify|tag|format|parse|extract|convert|summarize|json|xml|regex|validate|clean|normalize)\b/i },
|
|
55
|
+
];
|
|
56
|
+
function classifyTask(prompt) {
|
|
57
|
+
for (const { type, keywords } of TASK_PATTERNS) {
|
|
58
|
+
if (keywords.test(prompt))
|
|
59
|
+
return type;
|
|
60
|
+
}
|
|
61
|
+
return 'util';
|
|
62
|
+
}
|
|
63
|
+
function selectModel(prompt, requestedModel) {
|
|
64
|
+
if (requestedModel && requestedModel.includes('/')) {
|
|
65
|
+
return { model: requestedModel, taskType: classifyTask(prompt), autoClassified: false };
|
|
66
|
+
}
|
|
67
|
+
if (requestedModel && MODEL_ALIASES[requestedModel]) {
|
|
68
|
+
return { model: MODEL_ALIASES[requestedModel], taskType: classifyTask(prompt), autoClassified: false };
|
|
69
|
+
}
|
|
70
|
+
if (requestedModel && requestedModel in EXPERTISE_MODELS) {
|
|
71
|
+
const taskType = requestedModel;
|
|
72
|
+
return { model: EXPERTISE_MODELS[taskType].primary, taskType, autoClassified: false };
|
|
73
|
+
}
|
|
74
|
+
const taskType = classifyTask(prompt);
|
|
75
|
+
return { model: EXPERTISE_MODELS[taskType].primary, taskType, autoClassified: true };
|
|
76
|
+
}
|
|
77
|
+
function getFallbackModel(taskType) {
|
|
78
|
+
return EXPERTISE_MODELS[taskType].fallback;
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type MonotaskState = 'IDLE' | 'RESERVED' | 'ACTIVE';
|
|
2
|
+
export declare class MonotaskSM {
|
|
3
|
+
private static slots;
|
|
4
|
+
private static slot;
|
|
5
|
+
private static audit;
|
|
6
|
+
private static wipe;
|
|
7
|
+
/**
|
|
8
|
+
* Attempt to claim an agent for a task.
|
|
9
|
+
* Returns false if agent is already RESERVED or ACTIVE, or depth limit exceeded.
|
|
10
|
+
* @param depth Sub-agent chain depth (0 = top-level, 1 = first sub-agent, …)
|
|
11
|
+
*/
|
|
12
|
+
static claim(agentId: string, taskId: string, depth?: number): boolean;
|
|
13
|
+
static start(agentId: string, taskId: string): boolean;
|
|
14
|
+
static complete(agentId: string, taskId: string): void;
|
|
15
|
+
static release(agentId: string, taskId: string, reason?: string): void;
|
|
16
|
+
static getState(agentId: string): MonotaskState;
|
|
17
|
+
static getDepth(agentId: string): number;
|
|
18
|
+
static isIdle(agentId: string): boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AMD-08 Monotask State Machine
|
|
3
|
+
// IDLE → RESERVED → ACTIVE → IDLE (atomic, audit-logged)
|
|
4
|
+
// Enforces: one task per agent at a time, sub-agent chain depth ≤ 3
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MonotaskSM = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
10
|
+
const MAX_DEPTH = 3;
|
|
11
|
+
const AUDIT_DIR = (0, path_1.join)(process.cwd(), 'logs', 'monotask');
|
|
12
|
+
const AUDIT_FILE = (0, path_1.join)(AUDIT_DIR, 'audit.jsonl');
|
|
13
|
+
// ── State Machine (Singleton) ────────────────────────────────────────────────
|
|
14
|
+
class MonotaskSM {
|
|
15
|
+
static slots = new Map();
|
|
16
|
+
// ── Internal helpers ──────────────────────────────────────────────────────
|
|
17
|
+
static slot(agentId) {
|
|
18
|
+
if (!this.slots.has(agentId)) {
|
|
19
|
+
this.slots.set(agentId, { state: 'IDLE', taskId: null, claimedAt: null, depth: 0 });
|
|
20
|
+
}
|
|
21
|
+
return this.slots.get(agentId);
|
|
22
|
+
}
|
|
23
|
+
static audit(entry) {
|
|
24
|
+
try {
|
|
25
|
+
(0, fs_1.mkdirSync)(AUDIT_DIR, { recursive: true });
|
|
26
|
+
(0, fs_1.appendFileSync)(AUDIT_FILE, JSON.stringify(entry) + '\n', 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
process.stderr.write('[monotask] audit write failed\n');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
static wipe(slot) {
|
|
33
|
+
// Context Wipe Protocol (AMD-08 §3.3): clear all slot state
|
|
34
|
+
slot.state = 'IDLE';
|
|
35
|
+
slot.taskId = null;
|
|
36
|
+
slot.claimedAt = null;
|
|
37
|
+
slot.depth = 0;
|
|
38
|
+
}
|
|
39
|
+
// ── Transition: IDLE → RESERVED ──────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Attempt to claim an agent for a task.
|
|
42
|
+
* Returns false if agent is already RESERVED or ACTIVE, or depth limit exceeded.
|
|
43
|
+
* @param depth Sub-agent chain depth (0 = top-level, 1 = first sub-agent, …)
|
|
44
|
+
*/
|
|
45
|
+
static claim(agentId, taskId, depth = 0) {
|
|
46
|
+
const s = this.slot(agentId);
|
|
47
|
+
const from = s.state;
|
|
48
|
+
if (from !== 'IDLE') {
|
|
49
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: 'claim_rejected',
|
|
50
|
+
from, to: from, depth, reason: `Agent already ${from} on task ${s.taskId ?? '?'}` });
|
|
51
|
+
process.stderr.write(`[monotask] CLAIM REJECTED — ${agentId} is ${from} (task: ${s.taskId})\n`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (depth > MAX_DEPTH) {
|
|
55
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: 'depth_exceeded',
|
|
56
|
+
from, to: from, depth, reason: `Depth ${depth} > MAX_DEPTH ${MAX_DEPTH}` });
|
|
57
|
+
process.stderr.write(`[monotask] DEPTH EXCEEDED — ${agentId} depth ${depth} > ${MAX_DEPTH}\n`);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
s.state = 'RESERVED';
|
|
61
|
+
s.taskId = taskId;
|
|
62
|
+
s.claimedAt = new Date();
|
|
63
|
+
s.depth = depth;
|
|
64
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: 'IDLE→RESERVED',
|
|
65
|
+
from: 'IDLE', to: 'RESERVED', depth });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// ── Transition: RESERVED → ACTIVE ────────────────────────────────────────
|
|
69
|
+
static start(agentId, taskId) {
|
|
70
|
+
const s = this.slot(agentId);
|
|
71
|
+
const from = s.state;
|
|
72
|
+
if (from !== 'RESERVED') {
|
|
73
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: 'start_rejected',
|
|
74
|
+
from, to: from, reason: `Expected RESERVED, got ${from}` });
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
s.state = 'ACTIVE';
|
|
78
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: 'RESERVED→ACTIVE',
|
|
79
|
+
from: 'RESERVED', to: 'ACTIVE', depth: s.depth });
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
// ── Transition: ACTIVE → IDLE (successful completion) ────────────────────
|
|
83
|
+
static complete(agentId, taskId) {
|
|
84
|
+
const s = this.slot(agentId);
|
|
85
|
+
const from = s.state;
|
|
86
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: `${from}→IDLE`,
|
|
87
|
+
from, to: 'IDLE', depth: s.depth });
|
|
88
|
+
this.wipe(s);
|
|
89
|
+
}
|
|
90
|
+
// ── Transition: any → IDLE (error / rejection between retries) ───────────
|
|
91
|
+
static release(agentId, taskId, reason) {
|
|
92
|
+
const s = this.slot(agentId);
|
|
93
|
+
const from = s.state;
|
|
94
|
+
this.audit({ ts: new Date().toISOString(), agentId, taskId, transition: `${from}→IDLE(release)`,
|
|
95
|
+
from, to: 'IDLE', depth: s.depth, reason });
|
|
96
|
+
this.wipe(s);
|
|
97
|
+
}
|
|
98
|
+
// ── Queries ───────────────────────────────────────────────────────────────
|
|
99
|
+
static getState(agentId) {
|
|
100
|
+
return this.slot(agentId).state;
|
|
101
|
+
}
|
|
102
|
+
static getDepth(agentId) {
|
|
103
|
+
return this.slot(agentId).depth;
|
|
104
|
+
}
|
|
105
|
+
static isIdle(agentId) {
|
|
106
|
+
return this.slot(agentId).state === 'IDLE';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.MonotaskSM = MonotaskSM;
|
|
110
|
+
// ── Smoke test ────────────────────────────────────────────────────────────────
|
|
111
|
+
if (require.main === module) {
|
|
112
|
+
console.log('\n🤖 AMD-08 Monotask State Machine — Smoke Test\n');
|
|
113
|
+
const agent = 'test-agent';
|
|
114
|
+
// Happy path
|
|
115
|
+
console.assert(MonotaskSM.isIdle(agent), 'should start IDLE');
|
|
116
|
+
console.assert(MonotaskSM.claim(agent, 't1'), 'claim should succeed');
|
|
117
|
+
console.assert(MonotaskSM.getState(agent) === 'RESERVED', 'should be RESERVED');
|
|
118
|
+
console.assert(!MonotaskSM.claim(agent, 't2'), 'double-claim should fail');
|
|
119
|
+
console.assert(MonotaskSM.start(agent, 't1'), 'start should succeed');
|
|
120
|
+
console.assert(MonotaskSM.getState(agent) === 'ACTIVE', 'should be ACTIVE');
|
|
121
|
+
MonotaskSM.complete(agent, 't1');
|
|
122
|
+
console.assert(MonotaskSM.isIdle(agent), 'should be IDLE after complete');
|
|
123
|
+
// Depth guard
|
|
124
|
+
console.assert(MonotaskSM.claim('deep-agent', 'td', MAX_DEPTH + 1) === false, 'depth > MAX_DEPTH should fail');
|
|
125
|
+
// Release path
|
|
126
|
+
MonotaskSM.claim('r-agent', 'tr');
|
|
127
|
+
MonotaskSM.start('r-agent', 'tr');
|
|
128
|
+
MonotaskSM.release('r-agent', 'tr', 'rejection');
|
|
129
|
+
console.assert(MonotaskSM.isIdle('r-agent'), 'should be IDLE after release');
|
|
130
|
+
console.log('✅ PASS — audit log written to logs/monotask/audit.jsonl\n');
|
|
131
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* neutral-prompt-checker.ts — Sprint TICKET-005-RULE1
|
|
3
|
+
* Rule 1 (Neutral Prompts): detects answer-seeding in sprint descriptions.
|
|
4
|
+
*
|
|
5
|
+
* Answer-seeding = embedding specific implementation choices in a brief that
|
|
6
|
+
* should remain open-ended: code fences, import statements, copy-X patterns.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx ts-node scripts/governance/neutral-prompt-checker.ts --file=workspace/sprint-brief.md
|
|
10
|
+
* echo "description text" | npx ts-node scripts/governance/neutral-prompt-checker.ts
|
|
11
|
+
*
|
|
12
|
+
* Exit 0 = clean, Exit 1 = violations found
|
|
13
|
+
*/
|
|
14
|
+
export interface NeutralCheckResult {
|
|
15
|
+
clean: boolean;
|
|
16
|
+
violations: Array<{
|
|
17
|
+
line: number;
|
|
18
|
+
pattern: string;
|
|
19
|
+
text: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export declare function checkText(text: string): NeutralCheckResult;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* neutral-prompt-checker.ts — Sprint TICKET-005-RULE1
|
|
4
|
+
* Rule 1 (Neutral Prompts): detects answer-seeding in sprint descriptions.
|
|
5
|
+
*
|
|
6
|
+
* Answer-seeding = embedding specific implementation choices in a brief that
|
|
7
|
+
* should remain open-ended: code fences, import statements, copy-X patterns.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx ts-node scripts/governance/neutral-prompt-checker.ts --file=workspace/sprint-brief.md
|
|
11
|
+
* echo "description text" | npx ts-node scripts/governance/neutral-prompt-checker.ts
|
|
12
|
+
*
|
|
13
|
+
* Exit 0 = clean, Exit 1 = violations found
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.checkText = checkText;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
// Patterns that indicate answer-seeding
|
|
52
|
+
const SEEDING_RULES = [
|
|
53
|
+
{
|
|
54
|
+
pattern: 'code-fence',
|
|
55
|
+
regex: /^```/,
|
|
56
|
+
description: 'Code fence in brief — implementation details should not be pre-seeded',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
pattern: 'import-statement',
|
|
60
|
+
regex: /^\s*(import |from ['"]|require\()/,
|
|
61
|
+
description: 'Import statement — specific module choices should not be pre-seeded',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pattern: 'code-statement',
|
|
65
|
+
regex: /^\s*(const |let |var |function |class |export |async function )\w+/,
|
|
66
|
+
description: 'Code statement — implementation should not be pre-seeded',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pattern: 'copy-pattern',
|
|
70
|
+
regex: /\b(copy from|same as Sprint|exactly like|same pattern|use the same code|port from)\b/i,
|
|
71
|
+
description: 'Copy-from instruction — should describe intent, not implementation',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pattern: 'hardcoded-identifier',
|
|
75
|
+
regex: /\b([a-z][a-zA-Z]{14,}|[a-z]+(_[a-z]+){4,})\b/,
|
|
76
|
+
description: 'Very long identifier — may indicate pre-seeded variable/function name',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
function checkText(text) {
|
|
80
|
+
const lines = text.split('\n');
|
|
81
|
+
const violations = [];
|
|
82
|
+
let inCodeFence = false;
|
|
83
|
+
for (let i = 0; i < lines.length; i++) {
|
|
84
|
+
const line = lines[i];
|
|
85
|
+
// Track code fence state
|
|
86
|
+
if (line.trim().startsWith('```')) {
|
|
87
|
+
inCodeFence = !inCodeFence;
|
|
88
|
+
// Flag the opening fence itself (but not closing)
|
|
89
|
+
if (inCodeFence) {
|
|
90
|
+
violations.push({ line: i + 1, pattern: 'code-fence', text: line.trim().slice(0, 60) });
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Skip lines inside code fences (already flagged by opening)
|
|
95
|
+
if (inCodeFence)
|
|
96
|
+
continue;
|
|
97
|
+
for (const rule of SEEDING_RULES) {
|
|
98
|
+
if (rule.pattern === 'code-fence')
|
|
99
|
+
continue; // handled above
|
|
100
|
+
if (rule.regex.test(line)) {
|
|
101
|
+
violations.push({ line: i + 1, pattern: rule.pattern, text: line.trim().slice(0, 80) });
|
|
102
|
+
break; // one violation per line
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { clean: violations.length === 0, violations };
|
|
107
|
+
}
|
|
108
|
+
// CLI entrypoint
|
|
109
|
+
if (require.main === module) {
|
|
110
|
+
let text = '';
|
|
111
|
+
const fileArg = process.argv.find(a => a.startsWith('--file='));
|
|
112
|
+
if (fileArg) {
|
|
113
|
+
text = fs.readFileSync(fileArg.split('=')[1], 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
text = fs.readFileSync('/dev/stdin', 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
const result = checkText(text);
|
|
119
|
+
if (result.clean) {
|
|
120
|
+
console.log('✓ Rule 1 check PASS — no answer-seeding detected');
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.error(`✗ Rule 1 check FAIL — ${result.violations.length} violation(s):`);
|
|
125
|
+
result.violations.forEach(v => {
|
|
126
|
+
console.error(` Line ${v.line} [${v.pattern}]: ${v.text}`);
|
|
127
|
+
});
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* notion-direct.ts — Zero-dep Notion API client for senior-coder.
|
|
3
|
+
*
|
|
4
|
+
* Bypasses both OpenClaw and the @notionhq/client npm package (which would be a
|
|
5
|
+
* new dep). Uses raw HTTPS to api.notion.com.
|
|
6
|
+
*
|
|
7
|
+
* Public surface — only what the bot actually needs:
|
|
8
|
+
* - upsertSprint(sprint) → push a sprint to Notion Sprints DB (create or update by Sprint ID)
|
|
9
|
+
* - updateSprintStatus(id, ...) → patch a sprint's status/outcome after completion
|
|
10
|
+
* - createBlocker(blocker) → push a blocker to Notion Blockers DB
|
|
11
|
+
* - archiveDailyBrief(date,...) → create a sub-page under the Daily Briefs parent
|
|
12
|
+
* - queryNewSprintsInNotion() → pull sprints with Status=pending that don't exist locally yet
|
|
13
|
+
*
|
|
14
|
+
* Fail-open: every function returns {ok: false, reason} on error rather than
|
|
15
|
+
* throwing, so the bot pipeline doesn't break when Notion is down or the
|
|
16
|
+
* integration loses access. Callers log the failure and continue.
|
|
17
|
+
*
|
|
18
|
+
* Created: 2026-04-29.
|
|
19
|
+
*/
|
|
20
|
+
export interface NotionResult<T = unknown> {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
reason?: string;
|
|
23
|
+
data?: T;
|
|
24
|
+
}
|
|
25
|
+
export interface SprintRow {
|
|
26
|
+
sprint_id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
status: 'pending' | 'running' | 'done' | 'done-manual' | 'rejected' | 'loop-stuck' | 'skipped';
|
|
29
|
+
priority?: 'P0' | 'P1' | 'P2' | 'P3';
|
|
30
|
+
tasks_summary?: string;
|
|
31
|
+
authored_by?: 'human' | 'senior-coder' | 'swarm' | 'founder' | 'miner';
|
|
32
|
+
sprint_file_url?: string;
|
|
33
|
+
outcome?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface BlockerRow {
|
|
36
|
+
title: string;
|
|
37
|
+
sprint_page_id?: string;
|
|
38
|
+
detected_at_iso: string;
|
|
39
|
+
pick_count_24h: number;
|
|
40
|
+
status: 'open' | 'awaiting-dev' | 'awaiting-founder' | 'resolved' | 'wont-fix';
|
|
41
|
+
diagnosis?: string;
|
|
42
|
+
resolution?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Upsert a sprint row in Notion. Creates if not exists (by Sprint ID), updates if it does.
|
|
46
|
+
* Returns the page_id of the row in Notion (useful for setting Blocker.Sprint relation).
|
|
47
|
+
*/
|
|
48
|
+
export declare function upsertSprint(s: SprintRow): Promise<NotionResult<{
|
|
49
|
+
page_id: string;
|
|
50
|
+
}>>;
|
|
51
|
+
/**
|
|
52
|
+
* Lighter-weight update: only patches status (+ optional outcome). Use after sprint completion.
|
|
53
|
+
*/
|
|
54
|
+
export declare function updateSprintStatus(sprintId: string, status: SprintRow['status'], outcome?: string, tasksSummary?: string): Promise<NotionResult>;
|
|
55
|
+
export declare function createBlocker(b: BlockerRow): Promise<NotionResult<{
|
|
56
|
+
page_id: string;
|
|
57
|
+
}>>;
|
|
58
|
+
/**
|
|
59
|
+
* Create a sub-page under the Daily Briefs parent, titled by date, with the
|
|
60
|
+
* brief text as content. Notion pages support markdown-ish content via blocks;
|
|
61
|
+
* we use a single paragraph with the brief text (wrapped in code-block-style).
|
|
62
|
+
*/
|
|
63
|
+
export declare function archiveDailyBrief(dateIso: string, briefText: string): Promise<NotionResult<{
|
|
64
|
+
page_id: string;
|
|
65
|
+
}>>;
|
|
66
|
+
export interface NotionAuthoredSprint {
|
|
67
|
+
page_id: string;
|
|
68
|
+
sprint_id: string;
|
|
69
|
+
title: string;
|
|
70
|
+
status: string;
|
|
71
|
+
priority?: string;
|
|
72
|
+
tasks_summary?: string;
|
|
73
|
+
authored_by?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fetch all sprint rows in Notion with Status=pending. Caller compares against
|
|
77
|
+
* local `workspace/sprints/` to find which ones to materialize as JSON files.
|
|
78
|
+
*/
|
|
79
|
+
export declare function queryPendingSprintsFromNotion(): Promise<NotionResult<NotionAuthoredSprint[]>>;
|
|
80
|
+
/**
|
|
81
|
+
* Fetch ALL sprints in the Notion Sprints DB, return a Map of sprint_id → status.
|
|
82
|
+
* Used by sprint-runner.ts so Notion overrides local file state — protects against
|
|
83
|
+
* other Claude sessions reverting sprint files locally.
|
|
84
|
+
* Fail-safe: returns {ok: false} on Notion error so caller falls back to local-only.
|
|
85
|
+
*/
|
|
86
|
+
export declare function getAllNotionSprintStatuses(): Promise<NotionResult<Map<string, string>>>;
|
|
87
|
+
export declare function notionHealthCheck(): Promise<NotionResult<{
|
|
88
|
+
bot_name: string;
|
|
89
|
+
sprints_db: boolean;
|
|
90
|
+
blockers_db: boolean;
|
|
91
|
+
briefs_page: boolean;
|
|
92
|
+
}>>;
|