@ifi/oh-pi-ant-colony 0.2.12 → 0.2.14
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
CHANGED
|
@@ -183,7 +183,7 @@ export default function antColonyExtension(pi: ExtensionAPI) {
|
|
|
183
183
|
|
|
184
184
|
const isExplicitStatusRequest = (ctx: unknown): boolean => {
|
|
185
185
|
const text = lastUserMessageText(ctx);
|
|
186
|
-
return /(?:\/colony-status|bg_colony_status)|(?:
|
|
186
|
+
return /(?:\/colony-status|bg_colony_status)|(?:colony.{0,20}(?:status|progress|snapshot|update|check))|(?:(?:status|progress|snapshot|update|check).{0,20}colony)/i.test(
|
|
187
187
|
text,
|
|
188
188
|
);
|
|
189
189
|
};
|
|
@@ -2,7 +2,7 @@ import { makePheromoneId } from "./spawner.js";
|
|
|
2
2
|
import type { AntCaste, Pheromone, PheromoneType } from "./types.js";
|
|
3
3
|
|
|
4
4
|
const VALID_CASTES = new Set(["scout", "worker", "soldier", "drone"]);
|
|
5
|
-
const TASK_HEADER_RE = /^\s*#{2,6}\s*
|
|
5
|
+
const TASK_HEADER_RE = /^\s*#{2,6}\s*task\s*:\s*(.+?)\s*$/i;
|
|
6
6
|
|
|
7
7
|
export interface ParsedSubTask {
|
|
8
8
|
title: string;
|
|
@@ -25,13 +25,13 @@ function normalizeCaste(v: unknown): AntCaste {
|
|
|
25
25
|
if (VALID_CASTES.has(raw)) {
|
|
26
26
|
return raw as AntCaste;
|
|
27
27
|
}
|
|
28
|
-
if (raw.includes("
|
|
28
|
+
if (raw.includes("scout")) {
|
|
29
29
|
return "scout";
|
|
30
30
|
}
|
|
31
|
-
if (raw.includes("
|
|
31
|
+
if (raw.includes("worker")) {
|
|
32
32
|
return "worker";
|
|
33
33
|
}
|
|
34
|
-
if (raw.includes("
|
|
34
|
+
if (raw.includes("review") || raw.includes("soldier")) {
|
|
35
35
|
return "soldier";
|
|
36
36
|
}
|
|
37
37
|
if (raw.includes("drone") || raw.includes("bash") || raw.includes("shell")) {
|
|
@@ -41,10 +41,7 @@ function normalizeCaste(v: unknown): AntCaste {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function extractFileLike(value: string): string[] {
|
|
44
|
-
const normalized = value
|
|
45
|
-
.replace(/[,、;;]/g, ",")
|
|
46
|
-
.replace(/["']/g, "")
|
|
47
|
-
.replace(/`/g, "");
|
|
44
|
+
const normalized = value.replace(/;/g, ",").replace(/["']/g, "").replace(/`/g, "");
|
|
48
45
|
const tokens = normalized
|
|
49
46
|
.split(",")
|
|
50
47
|
.map((s) => s.trim())
|
|
@@ -53,21 +50,49 @@ function extractFileLike(value: string): string[] {
|
|
|
53
50
|
return [...new Set(fileish)];
|
|
54
51
|
}
|
|
55
52
|
|
|
53
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return typeof value === "object" && value !== null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasNonEmptyText(value: unknown): boolean {
|
|
58
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isJsonTaskLike(value: unknown): value is Record<string, unknown> {
|
|
62
|
+
return isRecord(value) && (hasNonEmptyText(value.title) || hasNonEmptyText(value.description));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractJsonTaskCandidates(parsed: unknown): Array<Record<string, unknown>> {
|
|
66
|
+
if (Array.isArray(parsed)) {
|
|
67
|
+
return parsed.filter(isJsonTaskLike);
|
|
68
|
+
}
|
|
69
|
+
if (isRecord(parsed) && Array.isArray(parsed.tasks)) {
|
|
70
|
+
return parsed.tasks.filter(isJsonTaskLike);
|
|
71
|
+
}
|
|
72
|
+
if (isJsonTaskLike(parsed)) {
|
|
73
|
+
return [parsed];
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
56
78
|
function normalizeJsonTasks(parsed: unknown): ParsedSubTask[] {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
return extractJsonTaskCandidates(parsed).map((t) => {
|
|
80
|
+
const title = String(t.title || t.description || "Untitled").trim() || "Untitled";
|
|
81
|
+
const description = String(t.description || t.title || title).trim() || title;
|
|
82
|
+
return {
|
|
83
|
+
title,
|
|
84
|
+
description,
|
|
85
|
+
files: Array.isArray(t.files)
|
|
86
|
+
? t.files
|
|
87
|
+
.map(String)
|
|
88
|
+
.map((f) => f.trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
: extractFileLike(String(t.files || "")),
|
|
91
|
+
caste: normalizeCaste(t.caste),
|
|
92
|
+
priority: normalizePriority(t.priority),
|
|
93
|
+
context: t.context ? String(t.context) : undefined,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
71
96
|
}
|
|
72
97
|
|
|
73
98
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Parser must handle many field variants (en/zh) and edge cases
|
|
@@ -95,7 +120,7 @@ function parseTasksFromStructuredLines(output: string): ParsedSubTask[] {
|
|
|
95
120
|
|
|
96
121
|
const fieldMatch = (line: string) => {
|
|
97
122
|
return line.match(
|
|
98
|
-
/^\s*(?:[-*]|\d+\.)?\s*(?:\*\*|__)?\s*(description|desc
|
|
123
|
+
/^\s*(?:[-*]|\d+\.)?\s*(?:\*\*|__)?\s*(description|desc|files?|caste|role|priority|prio|context)\s*(?:\*\*|__)?\s*:\s*(.*)$/i,
|
|
99
124
|
);
|
|
100
125
|
};
|
|
101
126
|
|
|
@@ -127,27 +152,27 @@ function parseTasksFromStructuredLines(output: string): ParsedSubTask[] {
|
|
|
127
152
|
const key = m[1].toLowerCase();
|
|
128
153
|
const value = (m[2] || "").trim();
|
|
129
154
|
|
|
130
|
-
if (["description", "desc"
|
|
155
|
+
if (["description", "desc"].includes(key)) {
|
|
131
156
|
current.description = value;
|
|
132
157
|
continue;
|
|
133
158
|
}
|
|
134
159
|
|
|
135
|
-
if (["files", "file"
|
|
160
|
+
if (["files", "file"].includes(key)) {
|
|
136
161
|
current.files.push(...extractFileLike(value));
|
|
137
162
|
continue;
|
|
138
163
|
}
|
|
139
164
|
|
|
140
|
-
if (["caste", "role"
|
|
165
|
+
if (["caste", "role"].includes(key)) {
|
|
141
166
|
current.caste = normalizeCaste(value);
|
|
142
167
|
continue;
|
|
143
168
|
}
|
|
144
169
|
|
|
145
|
-
if (["priority", "prio"
|
|
170
|
+
if (["priority", "prio"].includes(key)) {
|
|
146
171
|
current.priority = normalizePriority(value);
|
|
147
172
|
continue;
|
|
148
173
|
}
|
|
149
174
|
|
|
150
|
-
if (
|
|
175
|
+
if (key === "context") {
|
|
151
176
|
const contextLines = [value];
|
|
152
177
|
while (i + 1 < lines.length) {
|
|
153
178
|
const next = lines[i + 1];
|
|
@@ -173,13 +198,16 @@ export function parseSubTasks(output: string): ParsedSubTask[] {
|
|
|
173
198
|
const jsonMatch = output.match(/```json\s*([\s\S]*?)```/i);
|
|
174
199
|
if (jsonMatch?.[1]) {
|
|
175
200
|
try {
|
|
176
|
-
|
|
201
|
+
const jsonTasks = normalizeJsonTasks(JSON.parse(jsonMatch[1].trim()));
|
|
202
|
+
if (jsonTasks.length > 0) {
|
|
203
|
+
return jsonTasks;
|
|
204
|
+
}
|
|
177
205
|
} catch {
|
|
178
206
|
/* fallback */
|
|
179
207
|
}
|
|
180
208
|
}
|
|
181
209
|
|
|
182
|
-
// 2) Structured markdown task blocks
|
|
210
|
+
// 2) Structured markdown task blocks
|
|
183
211
|
return parseTasksFromStructuredLines(output);
|
|
184
212
|
}
|
|
185
213
|
|
|
@@ -107,9 +107,5 @@ export function buildPrompt(
|
|
|
107
107
|
if (task.context) {
|
|
108
108
|
prompt += `\n## Pre-loaded Context (from scout)\n${task.context}\n`;
|
|
109
109
|
}
|
|
110
|
-
if (/[\u4e00-\u9fff]/.test(task.description)) {
|
|
111
|
-
prompt +=
|
|
112
|
-
"\nIMPORTANT: Follow the language requirements specified in the task description. If the task says to write in Chinese, write in Chinese.\n";
|
|
113
|
-
}
|
|
114
110
|
return prompt;
|
|
115
111
|
}
|
|
@@ -259,7 +259,7 @@ export interface PlanValidation {
|
|
|
259
259
|
|
|
260
260
|
export function shouldUseScoutQuorum(goal: string): boolean {
|
|
261
261
|
// Multi-step/compound goals benefit from at least 2 scout votes
|
|
262
|
-
return /(\n\s*\d+[.)]
|
|
262
|
+
return /(\n\s*\d+[.)]|;| and |phase|then)/i.test(goal);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
export function decidePromoteOrFinalize(input: PromoteFinalizeGateInput): PromoteFinalizeGateDecision {
|