The previous commit incorrectly deleted all workflows. Only restart-gateway should be removed (replaced by direct shell trigger). Other workflows (solve-issue, extract-knowledge, develop-sense, develop-workflow) are CLI-triggered and independent of sense coupling.
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
import type { Dirent } from "node:fs";
|
|
import { readdir } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import type { StartStep, WorkflowMessage } from "@uncaged/nerve-core";
|
|
|
|
import type { ExplorerMeta } from "../roles/explorer.js";
|
|
import type { QuestionerMeta } from "../roles/questioner.js";
|
|
|
|
async function walkMarkdownFiles(rootDir: string, base: string): Promise<string[]> {
|
|
const out: string[] = [];
|
|
let entries: Dirent[];
|
|
try {
|
|
entries = (await readdir(rootDir, { withFileTypes: true })) as Dirent[];
|
|
} catch {
|
|
return out;
|
|
}
|
|
for (const e of entries) {
|
|
const name = e.name;
|
|
const rel = base ? `${base}/${name}` : name;
|
|
const full = join(rootDir, name);
|
|
if (e.isDirectory()) {
|
|
out.push(...(await walkMarkdownFiles(full, rel)));
|
|
} else if (e.isFile() && name.endsWith(".md")) {
|
|
out.push(rel.replace(/\\/g, "/"));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Enumerate all markdown files under `.knowledge/` as repo-relative paths; seed line first if present. */
|
|
export async function bootstrapKnowledgeQueue(cwd: string, startContent: string): Promise<string[]> {
|
|
const knowledgeDir = join(cwd, ".knowledge");
|
|
const relFiles = await walkMarkdownFiles(knowledgeDir, "");
|
|
const paths = relFiles.map((f) => `.knowledge/${f}`);
|
|
const seed = startContent.trim().split(/\r?\n/u)[0]?.trim() ?? "";
|
|
if (paths.length === 0 && seed.length > 0) {
|
|
return [seed];
|
|
}
|
|
if (seed.length > 0 && paths.includes(seed)) {
|
|
return [seed, ...paths.filter((p) => p !== seed)];
|
|
}
|
|
if (seed.length > 0 && !paths.includes(seed)) {
|
|
return [seed, ...paths];
|
|
}
|
|
return [...paths].sort();
|
|
}
|
|
|
|
function lastIndexOfRole(messages: WorkflowMessage[], role: string): number {
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
if (messages[i].role === role) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/** Next queue for questioner: bootstrap, or continue after answerer / explorer. */
|
|
export async function resolveQueueForQuestioner(
|
|
start: StartStep,
|
|
messages: WorkflowMessage[],
|
|
cwd: string,
|
|
): Promise<string[]> {
|
|
const lastQi = lastIndexOfRole(messages, "questioner");
|
|
if (lastQi === -1) {
|
|
return bootstrapKnowledgeQueue(cwd, start.content);
|
|
}
|
|
const qMeta = messages[lastQi].meta as QuestionerMeta;
|
|
const tail = messages.slice(lastQi + 1);
|
|
const explorerMsg = tail.find((m) => m.role === "explorer");
|
|
if (explorerMsg) {
|
|
const eMeta = explorerMsg.meta as ExplorerMeta;
|
|
return [...qMeta.remaining_queue, ...eMeta.new_cards];
|
|
}
|
|
return qMeta.remaining_queue;
|
|
}
|