@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,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* citizenship.ts — Kognai citizenship issuance for spawned agents.
|
|
4
|
+
*
|
|
5
|
+
* Founder rule (2026-05-27): every agent the swarm spawns is born as a
|
|
6
|
+
* Kognai citizen — not a bare agent. Citizenship means identity (citizen
|
|
7
|
+
* ID + roll number), constitutional binding (already done via the
|
|
8
|
+
* always-on preamble), a default Kōpus avatar config, an ACP baseline
|
|
9
|
+
* score, and registration in the citizenship registry.
|
|
10
|
+
*
|
|
11
|
+
* This module:
|
|
12
|
+
* - mintCitizen(name, foundingAgent, proposingAgent): assigns ID +
|
|
13
|
+
* roll number, generates a citizen.yaml payload, appends to the
|
|
14
|
+
* registry. Atomic tmp+rename writes.
|
|
15
|
+
* - readRegistry() / writeRegistry(): registry I/O.
|
|
16
|
+
*
|
|
17
|
+
* Stdlib only. No LLM, no network. Pure issuance.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.COMPANY_PREFIXES = void 0;
|
|
21
|
+
exports.mintCitizen = mintCitizen;
|
|
22
|
+
exports.lookupCitizen = lookupCitizen;
|
|
23
|
+
exports.renderCitizenYaml = renderCitizenYaml;
|
|
24
|
+
exports.readRegistry = readRegistry;
|
|
25
|
+
const node_fs_1 = require("node:fs");
|
|
26
|
+
const node_path_1 = require("node:path");
|
|
27
|
+
const node_crypto_1 = require("node:crypto");
|
|
28
|
+
const engine_paths_1 = require("./engine-paths");
|
|
29
|
+
// TICKET-215 Phase 3b-2: repo root via engine-paths (KOGNAI_ROOT / cwd) instead of
|
|
30
|
+
// `resolve(__dirname, '..', '..')`, so this module is location-independent inside
|
|
31
|
+
// @kognai/orchestrator-core. From a product repo root the resolved root is identical
|
|
32
|
+
// to the old __dirname/../.. — same workspace/citizens + agents paths, no behavior change.
|
|
33
|
+
const ROOT = (0, engine_paths_1.resolveEnginePaths)().root;
|
|
34
|
+
const REGISTRY_DIR = (0, node_path_1.join)(ROOT, 'workspace', 'citizens');
|
|
35
|
+
const REGISTRY_PATH = (0, node_path_1.join)(REGISTRY_DIR, 'registry.json');
|
|
36
|
+
const AGENTS_DIR = (0, node_path_1.join)(ROOT, 'agents');
|
|
37
|
+
/** Sibling companies in the Kognai ecosystem. Each has its own citizen
|
|
38
|
+
* prefix and its own rollNumber sequence inside the shared registry.
|
|
39
|
+
* Add new companies here as they come online (Achiri, SCS-001, DRI, ...). */
|
|
40
|
+
exports.COMPANY_PREFIXES = {
|
|
41
|
+
kognai: 'KGN',
|
|
42
|
+
voxight: 'VXG',
|
|
43
|
+
invoica: 'INV',
|
|
44
|
+
};
|
|
45
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Mint a new citizen. Assigns ID + roll number, writes the registry
|
|
48
|
+
* entry, returns the full record so callers can also persist citizen.yaml
|
|
49
|
+
* alongside the agent's other files.
|
|
50
|
+
*
|
|
51
|
+
* Idempotent by agent_name: if the agent already has a citizen record
|
|
52
|
+
* (e.g. retry / migration), returns the existing record instead of
|
|
53
|
+
* minting a duplicate.
|
|
54
|
+
*/
|
|
55
|
+
function mintCitizen(agent_name, opts = {}) {
|
|
56
|
+
if (!agent_name || typeof agent_name !== 'string') {
|
|
57
|
+
throw new Error('mintCitizen: agent_name is required');
|
|
58
|
+
}
|
|
59
|
+
// Founder fix 2026-05-27: reconcile registry from disk-of-record (each
|
|
60
|
+
// agent's citizen.yaml) before every mint. The registry.json was getting
|
|
61
|
+
// wiped between orchestrator runs (manual edits + git stash/pop cycles +
|
|
62
|
+
// race conditions between sequential orchestrator subprocesses), causing
|
|
63
|
+
// already-minted citizens to vanish and new mints to collide on roll
|
|
64
|
+
// numbers. citizen.yaml files are immutable source-of-truth; registry.json
|
|
65
|
+
// is a denormalized index that should always be derivable from them.
|
|
66
|
+
reconcileFromDisk();
|
|
67
|
+
const registry = readRegistry();
|
|
68
|
+
// Idempotency: lookup by (agent_name, company). Treat undefined company as
|
|
69
|
+
// 'kognai' so existing records (which predate the company field) match
|
|
70
|
+
// mints that explicitly target the kognai company.
|
|
71
|
+
const companyKey = opts.company; // undefined for legacy path
|
|
72
|
+
const existing = registry.citizens.find((c) => c.agent_name === agent_name && (c.company ?? 'kognai') === (companyKey ?? 'kognai'));
|
|
73
|
+
if (existing)
|
|
74
|
+
return existing;
|
|
75
|
+
const now = opts.now ?? new Date();
|
|
76
|
+
let citizen_id;
|
|
77
|
+
let rollNumber;
|
|
78
|
+
let agent_did;
|
|
79
|
+
if (companyKey) {
|
|
80
|
+
// Company-scoped path: per-company rollNumber, prefixed ID, scoped DID.
|
|
81
|
+
const prefix = exports.COMPANY_PREFIXES[companyKey] ?? companyKey.slice(0, 3).toUpperCase();
|
|
82
|
+
const companyRolls = registry.citizens
|
|
83
|
+
.filter((c) => (c.company ?? 'kognai') === companyKey)
|
|
84
|
+
.map((c) => c.rollNumber);
|
|
85
|
+
rollNumber = companyRolls.length > 0 ? Math.max(...companyRolls) + 1 : 1;
|
|
86
|
+
citizen_id = `${prefix}-${String(rollNumber).padStart(4, '0')}`;
|
|
87
|
+
agent_did = `did:kognai:${companyKey}:${agent_name}`;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Legacy Kognai-internal path: random hex ID + global rollNumber sequence.
|
|
91
|
+
citizen_id = `Citizen-${(0, node_crypto_1.randomBytes)(3).toString('hex').toUpperCase()}`;
|
|
92
|
+
rollNumber = registry.next_roll_number;
|
|
93
|
+
agent_did = `did:kognai:${agent_name}`;
|
|
94
|
+
}
|
|
95
|
+
const record = {
|
|
96
|
+
citizen_id,
|
|
97
|
+
rollNumber,
|
|
98
|
+
agent_name,
|
|
99
|
+
...(companyKey ? { company: companyKey } : {}),
|
|
100
|
+
agent_did,
|
|
101
|
+
mintedAt: now.toISOString(),
|
|
102
|
+
tier: opts.tier ?? 'I',
|
|
103
|
+
citizen_type: opts.citizen_type ?? 'spawned',
|
|
104
|
+
mascot: {
|
|
105
|
+
hue: Math.floor(Math.random() * 360),
|
|
106
|
+
state: 'idle',
|
|
107
|
+
},
|
|
108
|
+
reputation: 50, // ACP baseline per Sherlock v2 / TICKET-032
|
|
109
|
+
founding_agent: opts.founding_agent ?? 'ceo',
|
|
110
|
+
proposing_agent: opts.proposing_agent ?? 'cto',
|
|
111
|
+
origin_proposal_id: opts.origin_proposal_id,
|
|
112
|
+
};
|
|
113
|
+
registry.citizens.push(record);
|
|
114
|
+
registry.total = registry.citizens.length;
|
|
115
|
+
// Only advance the global next_roll_number on legacy mints — company-scoped
|
|
116
|
+
// mints have their own per-company counter computed at mint time.
|
|
117
|
+
if (!companyKey)
|
|
118
|
+
registry.next_roll_number = rollNumber + 1;
|
|
119
|
+
writeRegistry(registry);
|
|
120
|
+
return record;
|
|
121
|
+
}
|
|
122
|
+
/** Lookup an existing citizen by (agent_name, company). Returns null when
|
|
123
|
+
* no match. Treats undefined company as 'kognai' for legacy compatibility. */
|
|
124
|
+
function lookupCitizen(opts) {
|
|
125
|
+
reconcileFromDisk();
|
|
126
|
+
const registry = readRegistry();
|
|
127
|
+
const companyKey = opts.company ?? 'kognai';
|
|
128
|
+
const found = registry.citizens.find((c) => c.agent_name === opts.agent_name && (c.company ?? 'kognai') === companyKey);
|
|
129
|
+
return found ?? null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Render the citizen record as a citizen.yaml string for writing alongside
|
|
133
|
+
* agent.yaml in the agent's directory.
|
|
134
|
+
*/
|
|
135
|
+
function renderCitizenYaml(c) {
|
|
136
|
+
return `# Kognai Citizenship — issued at agent spawn (citizenship.ts)
|
|
137
|
+
# Every spawned agent is born as a Kognai citizen, not a bare worker.
|
|
138
|
+
citizen_id: ${c.citizen_id}
|
|
139
|
+
rollNumber: ${c.rollNumber}
|
|
140
|
+
agent_name: ${c.agent_name}
|
|
141
|
+
agent_did: ${c.agent_did}
|
|
142
|
+
mintedAt: "${c.mintedAt}"
|
|
143
|
+
tier: ${c.tier}
|
|
144
|
+
citizen_type: ${c.citizen_type}
|
|
145
|
+
mascot:
|
|
146
|
+
hue: ${c.mascot.hue}
|
|
147
|
+
state: ${c.mascot.state}
|
|
148
|
+
reputation: ${c.reputation}
|
|
149
|
+
founding_agent: ${c.founding_agent ?? 'ceo'}
|
|
150
|
+
proposing_agent: ${c.proposing_agent ?? 'cto'}
|
|
151
|
+
${c.origin_proposal_id ? `origin_proposal_id: ${c.origin_proposal_id}\n` : ''}`;
|
|
152
|
+
}
|
|
153
|
+
// ─── Registry I/O ─────────────────────────────────────────────────────────────
|
|
154
|
+
function readRegistry() {
|
|
155
|
+
if (!(0, node_fs_1.existsSync)(REGISTRY_PATH)) {
|
|
156
|
+
return { total: 0, next_roll_number: 1, citizens: [] };
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse((0, node_fs_1.readFileSync)(REGISTRY_PATH, 'utf-8'));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return { total: 0, next_roll_number: 1, citizens: [] };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function writeRegistry(reg) {
|
|
166
|
+
(0, node_fs_1.mkdirSync)(REGISTRY_DIR, { recursive: true });
|
|
167
|
+
const tmp = `${REGISTRY_PATH}.tmp.${process.pid}`;
|
|
168
|
+
(0, node_fs_1.writeFileSync)(tmp, JSON.stringify(reg, null, 2));
|
|
169
|
+
(0, node_fs_1.renameSync)(tmp, REGISTRY_PATH);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Walk agents/<name>/citizen.yaml files and merge any missing entries
|
|
173
|
+
* into registry.json. The YAML files are immutable source-of-truth; the
|
|
174
|
+
* registry is a denormalized index. This makes mintCitizen tolerant of
|
|
175
|
+
* registry-file resets, git stash cycles, manual edits, and race
|
|
176
|
+
* conditions between sequential orchestrator processes.
|
|
177
|
+
*
|
|
178
|
+
* Idempotent. Cheap (only reads YAMLs we don't already have in the index).
|
|
179
|
+
* No-op when AGENTS_DIR doesn't exist (tests / fresh checkouts).
|
|
180
|
+
*/
|
|
181
|
+
function reconcileFromDisk() {
|
|
182
|
+
if (!(0, node_fs_1.existsSync)(AGENTS_DIR))
|
|
183
|
+
return;
|
|
184
|
+
const registry = readRegistry();
|
|
185
|
+
const known = new Set(registry.citizens.map((c) => c.agent_name));
|
|
186
|
+
const agentDirs = (0, node_fs_1.readdirSync)(AGENTS_DIR, { withFileTypes: true })
|
|
187
|
+
.filter((d) => d.isDirectory())
|
|
188
|
+
.map((d) => d.name);
|
|
189
|
+
let added = 0;
|
|
190
|
+
let maxRoll = registry.citizens.reduce((m, c) => Math.max(m, c.rollNumber), 0);
|
|
191
|
+
for (const name of agentDirs) {
|
|
192
|
+
if (known.has(name))
|
|
193
|
+
continue;
|
|
194
|
+
const yamlPath = (0, node_path_1.join)(AGENTS_DIR, name, 'citizen.yaml');
|
|
195
|
+
if (!(0, node_fs_1.existsSync)(yamlPath))
|
|
196
|
+
continue; // not a Kognai citizen yet
|
|
197
|
+
try {
|
|
198
|
+
const raw = (0, node_fs_1.readFileSync)(yamlPath, 'utf-8');
|
|
199
|
+
const record = parseCitizenYaml(raw);
|
|
200
|
+
if (!record)
|
|
201
|
+
continue;
|
|
202
|
+
registry.citizens.push(record);
|
|
203
|
+
maxRoll = Math.max(maxRoll, record.rollNumber);
|
|
204
|
+
added++;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Ignore malformed YAML; will be re-attempted next call.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (added > 0) {
|
|
211
|
+
// Sort by mintedAt to keep the registry temporally ordered.
|
|
212
|
+
// Tolerate records with a missing/empty mintedAt by treating them as
|
|
213
|
+
// earliest-known — they get a placeholder timestamp at the head.
|
|
214
|
+
registry.citizens.sort((a, b) => (a.mintedAt || '').localeCompare(b.mintedAt || ''));
|
|
215
|
+
registry.total = registry.citizens.length;
|
|
216
|
+
registry.next_roll_number = maxRoll + 1;
|
|
217
|
+
writeRegistry(registry);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Minimal YAML parser scoped to citizen.yaml's known shape. We avoid a
|
|
221
|
+
* full YAML dependency for one file — stdlib only per the module's contract. */
|
|
222
|
+
function parseCitizenYaml(raw) {
|
|
223
|
+
const lines = raw.split('\n');
|
|
224
|
+
const flat = {};
|
|
225
|
+
const mascot = {};
|
|
226
|
+
let inMascot = false;
|
|
227
|
+
for (const lineRaw of lines) {
|
|
228
|
+
const line = lineRaw.replace(/#.*$/, '').trimEnd();
|
|
229
|
+
if (!line.trim() || line.startsWith('#'))
|
|
230
|
+
continue;
|
|
231
|
+
if (line.startsWith('mascot:')) {
|
|
232
|
+
inMascot = true;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (inMascot && /^\s+/.test(line)) {
|
|
236
|
+
const m = line.trim().match(/^([a-z_]+):\s*(.+)$/);
|
|
237
|
+
if (m) {
|
|
238
|
+
const [, k, v] = m;
|
|
239
|
+
if (k === 'hue')
|
|
240
|
+
mascot.hue = parseInt(v, 10);
|
|
241
|
+
if (k === 'state')
|
|
242
|
+
mascot.state = v;
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
inMascot = false;
|
|
247
|
+
const m = line.match(/^([a-z_]+):\s*(.*)$/);
|
|
248
|
+
if (!m)
|
|
249
|
+
continue;
|
|
250
|
+
const [, k, v] = m;
|
|
251
|
+
flat[k] = v.replace(/^"|"$/g, '').trim();
|
|
252
|
+
}
|
|
253
|
+
if (!flat.citizen_id || !flat.agent_name)
|
|
254
|
+
return null;
|
|
255
|
+
return {
|
|
256
|
+
citizen_id: flat.citizen_id,
|
|
257
|
+
rollNumber: parseInt(flat.rollNumber, 10),
|
|
258
|
+
agent_name: flat.agent_name,
|
|
259
|
+
agent_did: flat.agent_did || `did:kognai:${flat.agent_name}`,
|
|
260
|
+
mintedAt: flat.mintedAt,
|
|
261
|
+
tier: flat.tier || 'I',
|
|
262
|
+
citizen_type: flat.citizen_type || 'spawned',
|
|
263
|
+
mascot: {
|
|
264
|
+
hue: mascot.hue ?? 0,
|
|
265
|
+
state: mascot.state || 'idle',
|
|
266
|
+
},
|
|
267
|
+
reputation: flat.reputation ? parseInt(flat.reputation, 10) : 50,
|
|
268
|
+
founding_agent: flat.founding_agent,
|
|
269
|
+
proposing_agent: flat.proposing_agent,
|
|
270
|
+
origin_proposal_id: flat.origin_proposal_id,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clawrouter-client.ts — Local ClawRouter gateway client
|
|
3
|
+
*
|
|
4
|
+
* ClawRouter is a LOCAL proxy (port 18789) that handles all x402 payments
|
|
5
|
+
* from its own auto-generated wallet. Just send standard OpenAI-compatible
|
|
6
|
+
* requests — no EIP-3009 signing needed on our side.
|
|
7
|
+
*
|
|
8
|
+
* Wallet: 0x67521a36Cc04b8D91c57cDdc587A7EBAC200062F (50 USDC)
|
|
9
|
+
* Flow: POST /v1/chat/completions → gateway pays upstream → 200 response
|
|
10
|
+
*/
|
|
11
|
+
export interface ClawRouterOptions {
|
|
12
|
+
model: string;
|
|
13
|
+
prompt: string;
|
|
14
|
+
systemPrompt?: string;
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
think?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ClawRouterResult {
|
|
19
|
+
content: string;
|
|
20
|
+
model: string;
|
|
21
|
+
costUsdc: number;
|
|
22
|
+
backend: string;
|
|
23
|
+
inputTokens: number;
|
|
24
|
+
outputTokens: number;
|
|
25
|
+
}
|
|
26
|
+
interface CostEntry {
|
|
27
|
+
timestamp: string;
|
|
28
|
+
model: string;
|
|
29
|
+
costUsdc: number;
|
|
30
|
+
inputTokens: number;
|
|
31
|
+
outputTokens: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function getCostLog(): readonly CostEntry[];
|
|
34
|
+
export declare function getTotalSpent(): number;
|
|
35
|
+
export declare function callClawRouter(opts: ClawRouterOptions): Promise<ClawRouterResult>;
|
|
36
|
+
export declare function clawRouterIsAvailable(): Promise<boolean>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* clawrouter-client.ts — Local ClawRouter gateway client
|
|
4
|
+
*
|
|
5
|
+
* ClawRouter is a LOCAL proxy (port 18789) that handles all x402 payments
|
|
6
|
+
* from its own auto-generated wallet. Just send standard OpenAI-compatible
|
|
7
|
+
* requests — no EIP-3009 signing needed on our side.
|
|
8
|
+
*
|
|
9
|
+
* Wallet: 0x67521a36Cc04b8D91c57cDdc587A7EBAC200062F (50 USDC)
|
|
10
|
+
* Flow: POST /v1/chat/completions → gateway pays upstream → 200 response
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.getCostLog = getCostLog;
|
|
47
|
+
exports.getTotalSpent = getTotalSpent;
|
|
48
|
+
exports.callClawRouter = callClawRouter;
|
|
49
|
+
exports.clawRouterIsAvailable = clawRouterIsAvailable;
|
|
50
|
+
const http = __importStar(require("http"));
|
|
51
|
+
const https = __importStar(require("https"));
|
|
52
|
+
const CLAWROUTER_URL = process.env.CLAWROUTER_GATEWAY_URL || 'http://127.0.0.1:18789/v1'; // SEC3: loopback-only default
|
|
53
|
+
const costLog = [];
|
|
54
|
+
const MAX_COST_LOG = 1000;
|
|
55
|
+
function getCostLog() {
|
|
56
|
+
return costLog;
|
|
57
|
+
}
|
|
58
|
+
function getTotalSpent() {
|
|
59
|
+
return costLog.reduce((sum, e) => sum + e.costUsdc, 0);
|
|
60
|
+
}
|
|
61
|
+
function httpRequest(url, body, extraHeaders = {}) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const parsed = new URL(url);
|
|
64
|
+
const isHttps = parsed.protocol === 'https:';
|
|
65
|
+
const lib = isHttps ? https : http;
|
|
66
|
+
const req = lib.request({
|
|
67
|
+
hostname: parsed.hostname,
|
|
68
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
69
|
+
path: parsed.pathname + parsed.search,
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
'Content-Length': Buffer.byteLength(body),
|
|
74
|
+
...extraHeaders,
|
|
75
|
+
},
|
|
76
|
+
}, (res) => {
|
|
77
|
+
let data = '';
|
|
78
|
+
res.on('data', (chunk) => (data += chunk));
|
|
79
|
+
res.on('end', () => resolve({ status: res.statusCode || 0, data, headers: res.headers }));
|
|
80
|
+
});
|
|
81
|
+
req.on('error', reject);
|
|
82
|
+
req.setTimeout(300_000, () => {
|
|
83
|
+
req.destroy();
|
|
84
|
+
reject(new Error('ClawRouter request timeout (300s)'));
|
|
85
|
+
});
|
|
86
|
+
req.write(body);
|
|
87
|
+
req.end();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function callClawRouter(opts) {
|
|
91
|
+
const { model, prompt, systemPrompt, maxTokens = 4096 } = opts;
|
|
92
|
+
const chatUrl = `${CLAWROUTER_URL}/chat/completions`;
|
|
93
|
+
const messages = [];
|
|
94
|
+
if (systemPrompt)
|
|
95
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
96
|
+
messages.push({ role: 'user', content: prompt });
|
|
97
|
+
const requestBody = JSON.stringify({ model, messages, max_tokens: maxTokens });
|
|
98
|
+
const response = await httpRequest(chatUrl, requestBody);
|
|
99
|
+
if (response.status !== 200) {
|
|
100
|
+
throw new Error(`ClawRouter returned ${response.status}: ${response.data.slice(0, 300)}`);
|
|
101
|
+
}
|
|
102
|
+
const json = JSON.parse(response.data);
|
|
103
|
+
const costHeader = response.headers['x-payment-amount'];
|
|
104
|
+
const costUsdc = costHeader ? Number(costHeader) / 1_000_000 : 0;
|
|
105
|
+
const result = {
|
|
106
|
+
content: json.choices?.[0]?.message?.content || '',
|
|
107
|
+
model: json.model || model,
|
|
108
|
+
costUsdc,
|
|
109
|
+
backend: 'clawrouter',
|
|
110
|
+
inputTokens: json.usage?.prompt_tokens || 0,
|
|
111
|
+
outputTokens: json.usage?.completion_tokens || 0,
|
|
112
|
+
};
|
|
113
|
+
costLog.push({
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
model,
|
|
116
|
+
costUsdc: result.costUsdc,
|
|
117
|
+
inputTokens: result.inputTokens,
|
|
118
|
+
outputTokens: result.outputTokens,
|
|
119
|
+
});
|
|
120
|
+
if (costLog.length > MAX_COST_LOG)
|
|
121
|
+
costLog.shift();
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
async function clawRouterIsAvailable() {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const req = http.request({ hostname: '127.0.0.1', port: 18789, path: '/v1/models', method: 'GET' }, // SEC3: loopback-only
|
|
127
|
+
(res) => {
|
|
128
|
+
let data = '';
|
|
129
|
+
res.on('data', (chunk) => (data += chunk));
|
|
130
|
+
res.on('end', () => {
|
|
131
|
+
if (res.statusCode !== 200) {
|
|
132
|
+
resolve(false);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
JSON.parse(data);
|
|
137
|
+
resolve(true);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
resolve(false);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
req.on('error', () => resolve(false));
|
|
145
|
+
req.setTimeout(3000, () => { req.destroy(); resolve(false); });
|
|
146
|
+
req.end();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type AssetCategory = 'QUEUE' | 'DATABASE' | 'API' | 'AUTH' | 'PAYMENT' | 'STORAGE' | 'WORKER' | 'TESTING' | 'INFRASTRUCTURE' | 'INTELLIGENCE' | 'AGENT' | 'UI' | 'OTHER';
|
|
2
|
+
export type AssetTier = 1 | 2 | 3;
|
|
3
|
+
export interface CodeAsset {
|
|
4
|
+
asset_id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
category: AssetCategory;
|
|
8
|
+
tags: string[];
|
|
9
|
+
language: string;
|
|
10
|
+
dependencies: string[];
|
|
11
|
+
interface: string;
|
|
12
|
+
usage_example: string;
|
|
13
|
+
test_coverage: string;
|
|
14
|
+
quality_score: number;
|
|
15
|
+
production_validations: number;
|
|
16
|
+
origin: string;
|
|
17
|
+
provenance: string;
|
|
18
|
+
ip_status: 'eligible' | 'client-restricted' | 'unknown';
|
|
19
|
+
version: string;
|
|
20
|
+
last_modified: string;
|
|
21
|
+
usage_count: number;
|
|
22
|
+
known_limitations: string[];
|
|
23
|
+
related_assets: string[];
|
|
24
|
+
tier: AssetTier;
|
|
25
|
+
source_files: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface CrystalliseCodeAssetInput {
|
|
28
|
+
agentId: string;
|
|
29
|
+
sprintId: string;
|
|
30
|
+
taskId: string;
|
|
31
|
+
taskTitle: string;
|
|
32
|
+
files: string[];
|
|
33
|
+
supervisorScore: number;
|
|
34
|
+
origin: 'kognai-core' | 'invoica' | 'voxight' | string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Index code files from an approved sprint into the Code Asset Library.
|
|
38
|
+
* Only indexes files that are clearly reusable library code (lib/, scripts/lib/, agents/).
|
|
39
|
+
* New assets enter at Tier 3. Returns asset_id or null if skipped.
|
|
40
|
+
*/
|
|
41
|
+
export declare function crystalliseCodeAsset(input: CrystalliseCodeAssetInput): string | null;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Code Asset Crystalliser — AMD-07 Code Asset Library
|
|
3
|
+
// Post-sprint: extract, classify, and index reusable code artifacts.
|
|
4
|
+
// Runs automatically after every approved sprint (like skill-crystalliser.ts).
|
|
5
|
+
// Model: Qwen3-4B (LOCAL) for classification; falls back to no-op if unavailable.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.crystalliseCodeAsset = crystalliseCodeAsset;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const engine_paths_1 = require("./engine-paths");
|
|
12
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
13
|
+
const LIBRARY_DIR = (0, path_1.join)((0, engine_paths_1.resolveEnginePaths)().root, 'code_assets');
|
|
14
|
+
const INDEX_PATH = (0, path_1.join)(LIBRARY_DIR, 'index.json');
|
|
15
|
+
const ASSETS_DIR = (0, path_1.join)(LIBRARY_DIR, 'assets');
|
|
16
|
+
const PENDING_DIR = (0, path_1.join)(LIBRARY_DIR, 'pending_review');
|
|
17
|
+
const MIN_SCORE_FOR_INDEXING = 75; // below this: not indexed
|
|
18
|
+
function loadIndex() {
|
|
19
|
+
(0, fs_1.mkdirSync)(LIBRARY_DIR, { recursive: true });
|
|
20
|
+
if (!(0, fs_1.existsSync)(INDEX_PATH)) {
|
|
21
|
+
return {
|
|
22
|
+
_meta: {
|
|
23
|
+
schema: 'AMD-07-B v1.0',
|
|
24
|
+
created_at: new Date().toISOString().slice(0, 10),
|
|
25
|
+
total_assets: 0,
|
|
26
|
+
tier_counts: { '1': 0, '2': 0, '3': 0, 'pending': 0 },
|
|
27
|
+
categories: ['QUEUE', 'DATABASE', 'API', 'AUTH', 'PAYMENT', 'STORAGE', 'WORKER', 'TESTING', 'INFRASTRUCTURE', 'INTELLIGENCE', 'AGENT', 'UI', 'OTHER'],
|
|
28
|
+
},
|
|
29
|
+
assets: [],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return JSON.parse((0, fs_1.readFileSync)(INDEX_PATH, 'utf-8'));
|
|
33
|
+
}
|
|
34
|
+
function saveIndex(idx) {
|
|
35
|
+
(0, fs_1.writeFileSync)(INDEX_PATH, JSON.stringify(idx, null, 2), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
// ── Category classifier (heuristic — no LLM required for Phase 0) ─────────────
|
|
38
|
+
function classifyCategory(files, taskTitle) {
|
|
39
|
+
const combined = (files.join(' ') + ' ' + taskTitle).toLowerCase();
|
|
40
|
+
if (/queue|bull|worker|job|redis/.test(combined))
|
|
41
|
+
return 'QUEUE';
|
|
42
|
+
if (/database|sql|supabase|postgres|migration|pg/.test(combined))
|
|
43
|
+
return 'DATABASE';
|
|
44
|
+
if (/api|endpoint|route|http|rest|fetch/.test(combined))
|
|
45
|
+
return 'API';
|
|
46
|
+
if (/auth|jwt|oauth|session|token|login/.test(combined))
|
|
47
|
+
return 'AUTH';
|
|
48
|
+
if (/payment|stripe|x402|usdc|wallet/.test(combined))
|
|
49
|
+
return 'PAYMENT';
|
|
50
|
+
if (/storage|s3|upload|file|blob/.test(combined))
|
|
51
|
+
return 'STORAGE';
|
|
52
|
+
if (/test|spec|jest|vitest|mocha/.test(combined))
|
|
53
|
+
return 'TESTING';
|
|
54
|
+
if (/infra|docker|deploy|ci|config|env/.test(combined))
|
|
55
|
+
return 'INFRASTRUCTURE';
|
|
56
|
+
if (/intel|oracle|voxight|embed|vector/.test(combined))
|
|
57
|
+
return 'INTELLIGENCE';
|
|
58
|
+
if (/agent|prompt|swarm|orchestrat/.test(combined))
|
|
59
|
+
return 'AGENT';
|
|
60
|
+
if (/ui|component|react|html|css|tailwind/.test(combined))
|
|
61
|
+
return 'UI';
|
|
62
|
+
return 'OTHER';
|
|
63
|
+
}
|
|
64
|
+
function inferLanguage(files) {
|
|
65
|
+
const exts = files.map(f => f.split('.').pop() ?? '');
|
|
66
|
+
if (exts.includes('ts'))
|
|
67
|
+
return 'TypeScript';
|
|
68
|
+
if (exts.includes('py'))
|
|
69
|
+
return 'Python';
|
|
70
|
+
if (exts.includes('js'))
|
|
71
|
+
return 'JavaScript';
|
|
72
|
+
if (exts.includes('sql'))
|
|
73
|
+
return 'SQL';
|
|
74
|
+
if (exts.includes('sh'))
|
|
75
|
+
return 'Shell';
|
|
76
|
+
return 'Unknown';
|
|
77
|
+
}
|
|
78
|
+
function inferTags(files, title) {
|
|
79
|
+
const tokens = (files.join(' ') + ' ' + title)
|
|
80
|
+
.toLowerCase()
|
|
81
|
+
.split(/[\s\-_/.]+/)
|
|
82
|
+
.filter(t => t.length > 3 && !['from', 'with', 'that', 'this', 'into', 'async', 'await'].includes(t));
|
|
83
|
+
return Array.from(new Set(tokens)).slice(0, 8);
|
|
84
|
+
}
|
|
85
|
+
// ── Main export: crystalliseCodeAsset ─────────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Index code files from an approved sprint into the Code Asset Library.
|
|
88
|
+
* Only indexes files that are clearly reusable library code (lib/, scripts/lib/, agents/).
|
|
89
|
+
* New assets enter at Tier 3. Returns asset_id or null if skipped.
|
|
90
|
+
*/
|
|
91
|
+
function crystalliseCodeAsset(input) {
|
|
92
|
+
// Gate: only index above minimum score
|
|
93
|
+
if (input.supervisorScore < MIN_SCORE_FOR_INDEXING) {
|
|
94
|
+
process.stderr.write(`[code-asset] Score ${input.supervisorScore} < ${MIN_SCORE_FOR_INDEXING} — skipping ${input.taskId}\n`);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
// Filter to reusable library files only (exclude workspace/ identity docs)
|
|
98
|
+
const eligibleFiles = input.files.filter(f => !f.startsWith('workspace/') && (/\/(lib|utils|helpers|agents)\//i.test(f) ||
|
|
99
|
+
f.endsWith('.sql') ||
|
|
100
|
+
(f.includes('/scripts/') && !f.includes('.test.'))));
|
|
101
|
+
if (eligibleFiles.length === 0) {
|
|
102
|
+
return null; // no reusable library code in this sprint
|
|
103
|
+
}
|
|
104
|
+
const idx = loadIndex();
|
|
105
|
+
// Dedup: skip if this exact task (sprint+title) is already indexed
|
|
106
|
+
const alreadyIndexed = idx.assets.some(a => a.provenance === input.sprintId && a.title === input.taskTitle);
|
|
107
|
+
if (alreadyIndexed) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const assetId = (0, crypto_1.randomUUID)();
|
|
111
|
+
const category = classifyCategory(eligibleFiles, input.taskTitle);
|
|
112
|
+
const language = inferLanguage(eligibleFiles);
|
|
113
|
+
const tags = inferTags(eligibleFiles, input.taskTitle);
|
|
114
|
+
const asset = {
|
|
115
|
+
asset_id: assetId,
|
|
116
|
+
title: input.taskTitle,
|
|
117
|
+
description: `Auto-indexed from sprint ${input.sprintId} task ${input.taskId}`,
|
|
118
|
+
category,
|
|
119
|
+
tags,
|
|
120
|
+
language,
|
|
121
|
+
dependencies: [],
|
|
122
|
+
interface: '', // Phase 0: populated manually or by future Code Asset Agent LLM pass
|
|
123
|
+
usage_example: '',
|
|
124
|
+
test_coverage: 'none',
|
|
125
|
+
quality_score: input.supervisorScore,
|
|
126
|
+
production_validations: 1,
|
|
127
|
+
origin: input.origin,
|
|
128
|
+
provenance: input.sprintId,
|
|
129
|
+
ip_status: 'eligible',
|
|
130
|
+
version: '1.0.0',
|
|
131
|
+
last_modified: new Date().toISOString().slice(0, 10),
|
|
132
|
+
usage_count: 0,
|
|
133
|
+
known_limitations: [],
|
|
134
|
+
related_assets: [],
|
|
135
|
+
tier: 3, // all new assets enter at Tier 3
|
|
136
|
+
source_files: eligibleFiles,
|
|
137
|
+
};
|
|
138
|
+
// Write asset schema to assets/{assetId}/schema.json
|
|
139
|
+
const assetDir = (0, path_1.join)(ASSETS_DIR, assetId);
|
|
140
|
+
(0, fs_1.mkdirSync)(assetDir, { recursive: true });
|
|
141
|
+
(0, fs_1.writeFileSync)((0, path_1.join)(assetDir, 'schema.json'), JSON.stringify(asset, null, 2), 'utf-8');
|
|
142
|
+
// Update index
|
|
143
|
+
idx.assets.push({
|
|
144
|
+
asset_id: asset.asset_id,
|
|
145
|
+
title: asset.title,
|
|
146
|
+
category: asset.category,
|
|
147
|
+
tags: asset.tags,
|
|
148
|
+
tier: asset.tier,
|
|
149
|
+
quality_score: asset.quality_score,
|
|
150
|
+
language: asset.language,
|
|
151
|
+
usage_count: asset.usage_count,
|
|
152
|
+
provenance: asset.provenance,
|
|
153
|
+
});
|
|
154
|
+
idx._meta.total_assets++;
|
|
155
|
+
idx._meta.tier_counts['3'] = (idx._meta.tier_counts['3'] || 0) + 1;
|
|
156
|
+
saveIndex(idx);
|
|
157
|
+
process.stdout.write(`[code-asset] Indexed ${assetId} (${category}, Tier 3, score ${input.supervisorScore}) ← ${input.sprintId}\n`);
|
|
158
|
+
return assetId;
|
|
159
|
+
}
|
|
160
|
+
// ── Smoke test ────────────────────────────────────────────────────────────────
|
|
161
|
+
if (require.main === module) {
|
|
162
|
+
console.log('\n📚 Code Asset Crystalliser — Smoke Test\n');
|
|
163
|
+
const id = crystalliseCodeAsset({
|
|
164
|
+
agentId: 'smoke-coder',
|
|
165
|
+
sprintId: 'sprint-000-smoke',
|
|
166
|
+
taskId: '000-01',
|
|
167
|
+
taskTitle: 'BrainX PostgreSQL memory client with pgvector',
|
|
168
|
+
files: ['scripts/lib/brainx-client.ts', 'scripts/lib/brainx-embed.ts', 'scripts/lib/brainx-schema.sql'],
|
|
169
|
+
supervisorScore: 88,
|
|
170
|
+
origin: 'kognai-core',
|
|
171
|
+
});
|
|
172
|
+
if (id) {
|
|
173
|
+
console.log(`✅ crystalliseCodeAsset() → asset_id: ${id}`);
|
|
174
|
+
const idx = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)((0, engine_paths_1.resolveEnginePaths)().root, 'code_assets/index.json'), 'utf-8'));
|
|
175
|
+
console.log(`✅ index.json total_assets: ${idx._meta.total_assets}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log('⚠️ Skipped (score below threshold or no lib files)');
|
|
179
|
+
}
|
|
180
|
+
console.log('\n✅ PASS\n');
|
|
181
|
+
}
|