@selesai/code 0.5.13 → 0.5.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 +186 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/extensions/package.json +4 -3
- package/dist/extensions/pi-powerline-footer/README.md +1 -1
- package/dist/extensions/pi-powerline-footer/guide.ts +227 -0
- package/dist/extensions/pi-powerline-footer/index.ts +88 -55
- package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -78
- package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +102 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +65 -30
- package/dist/skills/caveman/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/workflow-creation/SKILL.md +1 -0
- package/package.json +23 -2
- package/dist/agents/architect.md +0 -217
- package/dist/agents/builder.md +0 -120
- package/dist/agents/commentator.md +0 -130
- package/dist/agents/explorer.md +0 -51
- package/dist/agents/recapper.md +0 -26
- package/dist/extensions/gitignore-guard.ts +0 -132
- package/dist/extensions/hooks/claude-codex-hooks.json +0 -44
- package/dist/extensions/hooks/copilot-hooks.json +0 -21
- package/dist/extensions/hooks/ponytail-activate.js +0 -91
- package/dist/extensions/hooks/ponytail-config.js +0 -122
- package/dist/extensions/hooks/ponytail-instructions.js +0 -94
- package/dist/extensions/hooks/ponytail-mode-tracker.js +0 -55
- package/dist/extensions/hooks/ponytail-runtime.js +0 -68
- package/dist/extensions/hooks/ponytail-statusline.ps1 +0 -21
- package/dist/extensions/hooks/ponytail-statusline.sh +0 -12
- package/dist/extensions/hooks/ponytail-subagent.js +0 -22
- package/dist/extensions/pi-extension/index.js +0 -189
- package/dist/extensions/pi-extension/package.json +0 -8
- package/dist/extensions/pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
- package/dist/extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +0 -46
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +0 -45
- package/dist/extensions/pi-subagents/tsconfig.runner.json +0 -17
- package/dist/extensions/ponytail-pi-extension/index.js +0 -189
- package/dist/extensions/ponytail-pi-extension/package.json +0 -8
- package/dist/extensions/ponytail-pi-extension/test/extension.test.js +0 -167
- package/dist/extensions/ponytail-pi-extension/test/helpers.test.js +0 -92
- package/dist/extensions/prototype.ts +0 -713
- package/dist/extensions/question.ts +0 -350
- package/dist/extensions/quick.ts +0 -630
- package/dist/extensions/tps-tracker.ts +0 -280
- package/dist/extensions/workflow/task-validators.ts +0 -69
- package/dist/skills/selesai-default/SKILL.md +0 -16
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TPS Tracker Extension
|
|
3
|
-
*
|
|
4
|
-
* Tracks tokens per second during model generation and reports
|
|
5
|
-
* final TPS statistics at the end of each agent run.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
|
|
10
|
-
export default function (pi: ExtensionAPI) {
|
|
11
|
-
/** Timestamp when the current assistant message event started. Used as a fallback. */
|
|
12
|
-
let messageStart: number | null = null;
|
|
13
|
-
/** Timestamp of the first streamed output delta for the current assistant message. */
|
|
14
|
-
let streamStart: number | null = null;
|
|
15
|
-
/** Estimated streamed output tokens for live display before providers report final usage. */
|
|
16
|
-
let estimatedStreamedTokens = 0;
|
|
17
|
-
/** Cumulative official output tokens across all assistant messages in this agent run. */
|
|
18
|
-
let totalOutputTokens = 0;
|
|
19
|
-
/** Cumulative time (ms) spent actually streaming output deltas (excludes tool execution and first-token latency). */
|
|
20
|
-
let totalStreamMs = 0;
|
|
21
|
-
/** Subagent tool-call start times, used to include pi-subagents child output in aggregate TPS. */
|
|
22
|
-
const subagentStarts = new Map<string, number>();
|
|
23
|
-
/** Latest cumulative foreground subagent tokens seen during tool_execution_update. */
|
|
24
|
-
const subagentLiveTokens = new Map<string, number>();
|
|
25
|
-
/** Cumulative subagent output tokens in this agent run. */
|
|
26
|
-
let totalSubagentTokens = 0;
|
|
27
|
-
/** Cumulative subagent wall time (ms) in this agent run. */
|
|
28
|
-
let totalSubagentMs = 0;
|
|
29
|
-
/** Completed subagent call summaries in this agent run. */
|
|
30
|
-
const subagentSummaries: Array<{ tokens: number; ms: number; tps: number }> = [];
|
|
31
|
-
|
|
32
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
33
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function numberValue(value: unknown): number {
|
|
37
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getDetailsResult(value: unknown): Record<string, unknown> {
|
|
41
|
-
const result = asRecord(value);
|
|
42
|
-
const details = asRecord(result?.details);
|
|
43
|
-
return details ?? result ?? {};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getResults(value: unknown): Record<string, unknown>[] {
|
|
47
|
-
const details = getDetailsResult(value);
|
|
48
|
-
return Array.isArray(details.results)
|
|
49
|
-
? details.results.map(asRecord).filter((item): item is Record<string, unknown> => !!item)
|
|
50
|
-
: [];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function generatedTokensFromUsage(usage: Record<string, unknown> | null): number {
|
|
54
|
-
return numberValue(usage?.output ?? usage?.outputTokens ?? usage?.completionTokens ?? usage?.completion_tokens);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function generatedTokensFromMessage(message: { usage?: unknown; content?: unknown }): number {
|
|
58
|
-
const usageTokens = generatedTokensFromUsage(asRecord(message.usage));
|
|
59
|
-
if (usageTokens > 0) return usageTokens;
|
|
60
|
-
|
|
61
|
-
const content = Array.isArray(message.content) ? message.content : [];
|
|
62
|
-
const chars = content.reduce((sum, block) => {
|
|
63
|
-
const item = asRecord(block);
|
|
64
|
-
if (!item) return sum;
|
|
65
|
-
if (item.type === "text") return sum + String(item.text ?? "").length;
|
|
66
|
-
if (item.type === "thinking") return sum + String(item.thinking ?? "").length;
|
|
67
|
-
if (item.type === "toolCall") return sum + String(item.name ?? "").length + JSON.stringify(item.arguments ?? {}).length;
|
|
68
|
-
return sum;
|
|
69
|
-
}, 0);
|
|
70
|
-
return Math.ceil(chars / 4);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function outputTokensFromResult(value: unknown): number {
|
|
74
|
-
const results = getResults(value);
|
|
75
|
-
return results.reduce((sum, result) => sum + generatedTokensFromUsage(asRecord(result.usage)), 0);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function liveTokensFromResult(value: unknown): number {
|
|
79
|
-
return subagentProgressItems(value).reduce((sum, item) => sum + item.tokens, 0);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function subagentProgressItems(value: unknown): Array<{ name: string; tokens: number; status?: string }> {
|
|
83
|
-
const details = getDetailsResult(value);
|
|
84
|
-
const progress = Array.isArray(details.progress) ? details.progress : undefined;
|
|
85
|
-
const resultItems = getResults(value).map((result, index) => {
|
|
86
|
-
const progress = asRecord(result.progress);
|
|
87
|
-
const usage = asRecord(result.usage);
|
|
88
|
-
return {
|
|
89
|
-
name: String(result.agent || progress?.agent || `agent${index + 1}`),
|
|
90
|
-
tokens: generatedTokensFromUsage(usage) || generatedTokensFromUsage(progress),
|
|
91
|
-
status: typeof progress?.status === "string" ? progress.status : undefined,
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
if (!progress || resultItems.some((item) => item.tokens > 0)) return resultItems;
|
|
95
|
-
|
|
96
|
-
return progress.map((entry, index) => {
|
|
97
|
-
const item = asRecord(entry) ?? {};
|
|
98
|
-
return {
|
|
99
|
-
name: String(item.agent || `agent${index + 1}`),
|
|
100
|
-
tokens: generatedTokensFromUsage(item),
|
|
101
|
-
status: typeof item.status === "string" ? item.status : undefined,
|
|
102
|
-
};
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function subagentModeLabel(value: unknown): string {
|
|
107
|
-
const details = getDetailsResult(value);
|
|
108
|
-
const mode = typeof details.mode === "string" ? details.mode : "subagents";
|
|
109
|
-
return mode === "parallel" ? "parallel" : mode === "chain" ? "chain" : "subagent";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
113
|
-
totalOutputTokens = 0;
|
|
114
|
-
totalStreamMs = 0;
|
|
115
|
-
totalSubagentTokens = 0;
|
|
116
|
-
totalSubagentMs = 0;
|
|
117
|
-
subagentSummaries.length = 0;
|
|
118
|
-
messageStart = null;
|
|
119
|
-
streamStart = null;
|
|
120
|
-
estimatedStreamedTokens = 0;
|
|
121
|
-
const theme = ctx.ui.theme;
|
|
122
|
-
subagentStarts.clear();
|
|
123
|
-
subagentLiveTokens.clear();
|
|
124
|
-
ctx.ui.setStatus("tps", theme.fg("success", "⏱ generating..."));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
pi.on("message_start", async (event) => {
|
|
128
|
-
if (event.message.role !== "assistant") return;
|
|
129
|
-
messageStart = Date.now();
|
|
130
|
-
streamStart = null;
|
|
131
|
-
estimatedStreamedTokens = 0;
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
pi.on("message_update", async (event, ctx) => {
|
|
135
|
-
if (event.message.role !== "assistant") return;
|
|
136
|
-
|
|
137
|
-
const streamEvent = event.assistantMessageEvent;
|
|
138
|
-
const isOutputDelta =
|
|
139
|
-
streamEvent.type === "text_delta" ||
|
|
140
|
-
streamEvent.type === "thinking_delta" ||
|
|
141
|
-
streamEvent.type === "toolcall_delta";
|
|
142
|
-
|
|
143
|
-
if (!isOutputDelta) return;
|
|
144
|
-
|
|
145
|
-
const now = Date.now();
|
|
146
|
-
streamStart ??= now;
|
|
147
|
-
estimatedStreamedTokens += Math.max(0, streamEvent.delta.length / 4);
|
|
148
|
-
|
|
149
|
-
const elapsed = (now - streamStart) / 1000;
|
|
150
|
-
const officialTokens = generatedTokensFromUsage(asRecord(event.message.usage));
|
|
151
|
-
const currentTokens = officialTokens > 0 ? officialTokens : estimatedStreamedTokens;
|
|
152
|
-
|
|
153
|
-
if (elapsed > 0 && currentTokens > 0) {
|
|
154
|
-
const tps = Math.round(currentTokens / elapsed);
|
|
155
|
-
const tokenLabel = officialTokens > 0
|
|
156
|
-
? `${officialTokens} tok`
|
|
157
|
-
: `~${Math.round(estimatedStreamedTokens)} tok`;
|
|
158
|
-
const theme = ctx.ui.theme;
|
|
159
|
-
ctx.ui.setStatus(
|
|
160
|
-
"tps",
|
|
161
|
-
`${theme.fg("accent", `${tps} tok/s`)} ${theme.fg("success", `(${tokenLabel} / ${elapsed.toFixed(1)}s)`)}`,
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
pi.on("message_end", async (event) => {
|
|
167
|
-
if (event.message.role !== "assistant") return;
|
|
168
|
-
|
|
169
|
-
const messageTokens = generatedTokensFromMessage(event.message);
|
|
170
|
-
const timingStart = streamStart ?? messageStart;
|
|
171
|
-
if (!timingStart || messageTokens <= 0) {
|
|
172
|
-
messageStart = null;
|
|
173
|
-
streamStart = null;
|
|
174
|
-
estimatedStreamedTokens = 0;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
totalOutputTokens += messageTokens;
|
|
179
|
-
totalStreamMs += Math.max(0, Date.now() - timingStart);
|
|
180
|
-
|
|
181
|
-
messageStart = null;
|
|
182
|
-
streamStart = null;
|
|
183
|
-
estimatedStreamedTokens = 0;
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
pi.on("tool_execution_start", async (event) => {
|
|
187
|
-
if (event.toolName !== "subagent") return;
|
|
188
|
-
subagentStarts.set(event.toolCallId, Date.now());
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
pi.on("tool_execution_update", async (event, ctx) => {
|
|
192
|
-
if (event.toolName !== "subagent") return;
|
|
193
|
-
|
|
194
|
-
const start = subagentStarts.get(event.toolCallId);
|
|
195
|
-
if (!start) return;
|
|
196
|
-
|
|
197
|
-
const items = subagentProgressItems(event.partialResult);
|
|
198
|
-
const tokens = items.reduce((sum, item) => sum + item.tokens, 0);
|
|
199
|
-
subagentLiveTokens.set(event.toolCallId, tokens);
|
|
200
|
-
const elapsed = (Date.now() - start) / 1000;
|
|
201
|
-
if (tokens <= 0 || elapsed <= 0) return;
|
|
202
|
-
|
|
203
|
-
const tps = Math.round(tokens / elapsed);
|
|
204
|
-
const mode = subagentModeLabel(event.partialResult);
|
|
205
|
-
const agentSummary = items
|
|
206
|
-
.filter((item) => item.tokens > 0 || item.status === "running")
|
|
207
|
-
.map((item) => `${item.name} ${Math.round(item.tokens / elapsed)} t/s`)
|
|
208
|
-
.join(" ");
|
|
209
|
-
const theme = ctx.ui.theme;
|
|
210
|
-
ctx.ui.setStatus("tps", `${theme.fg("accent", `main+${mode} ${tps} t/s`)} ${theme.fg("success", `(${agentSummary || `${tps} t/s`})`)}`);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
pi.on("tool_execution_end", async (event, ctx) => {
|
|
214
|
-
if (event.toolName !== "subagent") return;
|
|
215
|
-
|
|
216
|
-
const start = subagentStarts.get(event.toolCallId);
|
|
217
|
-
subagentStarts.delete(event.toolCallId);
|
|
218
|
-
const liveTokens = subagentLiveTokens.get(event.toolCallId) ?? liveTokensFromResult(event.result);
|
|
219
|
-
subagentLiveTokens.delete(event.toolCallId);
|
|
220
|
-
if (!start || event.isError) return;
|
|
221
|
-
|
|
222
|
-
const outputTokens = outputTokensFromResult(event.result);
|
|
223
|
-
const displayTokens = outputTokens || liveTokens;
|
|
224
|
-
const mode = subagentModeLabel(event.result);
|
|
225
|
-
const elapsedMs = Math.max(0, Date.now() - start);
|
|
226
|
-
if (displayTokens <= 0 || elapsedMs <= 0) return;
|
|
227
|
-
const elapsedSeconds = elapsedMs / 1000;
|
|
228
|
-
const items = subagentProgressItems(event.result);
|
|
229
|
-
const agentSummary = items
|
|
230
|
-
.filter((item) => item.tokens > 0)
|
|
231
|
-
.map((item) => `${item.name} ${Math.round(item.tokens / elapsedSeconds)} t/s`)
|
|
232
|
-
.join(" ");
|
|
233
|
-
|
|
234
|
-
totalOutputTokens += displayTokens;
|
|
235
|
-
totalStreamMs += elapsedMs;
|
|
236
|
-
totalSubagentTokens += displayTokens;
|
|
237
|
-
totalSubagentMs += elapsedMs;
|
|
238
|
-
|
|
239
|
-
const tps = Math.round(displayTokens / elapsedSeconds);
|
|
240
|
-
subagentSummaries.push({ tokens: displayTokens, ms: elapsedMs, tps });
|
|
241
|
-
const theme = ctx.ui.theme;
|
|
242
|
-
const detail = theme.fg("success", `${mode}: ${displayTokens} tokens in ${(elapsedMs / 1000).toFixed(1)}s${agentSummary ? ` (${agentSummary})` : ""}`);
|
|
243
|
-
ctx.ui.notify(`${theme.fg("success", "✓")} ${theme.fg("accent", `${tps} t/s`)} ${detail}`, "info");
|
|
244
|
-
ctx.ui.setStatus(
|
|
245
|
-
"tps",
|
|
246
|
-
`${theme.fg("accent", `main+${mode} ${tps} t/s`)} ${theme.fg("success", `(${agentSummary || `${tps} t/s`})`)}`,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
251
|
-
const elapsed = totalStreamMs / 1000;
|
|
252
|
-
const tps = totalOutputTokens > 0 && elapsed > 0 ? Math.round(totalOutputTokens / elapsed) : 0;
|
|
253
|
-
|
|
254
|
-
const theme = ctx.ui.theme;
|
|
255
|
-
const icon = theme.fg("success", "✓");
|
|
256
|
-
const tpsLabel = tps > 0
|
|
257
|
-
? theme.fg("accent", `${tps} t/s`)
|
|
258
|
-
: theme.fg("success", "N/A");
|
|
259
|
-
const mainTokens = totalOutputTokens - totalSubagentTokens;
|
|
260
|
-
const mainMs = totalStreamMs - totalSubagentMs;
|
|
261
|
-
const mainTps = mainTokens > 0 && mainMs > 0 ? Math.round(mainTokens / (mainMs / 1000)) : 0;
|
|
262
|
-
const subagentTps = totalSubagentTokens > 0 && totalSubagentMs > 0
|
|
263
|
-
? Math.round(totalSubagentTokens / (totalSubagentMs / 1000))
|
|
264
|
-
: 0;
|
|
265
|
-
const mainDetail = mainTps > 0 ? theme.fg("success", ` | main ${mainTps} t/s`) : "";
|
|
266
|
-
const subagentDetail = subagentTps > 0
|
|
267
|
-
? theme.fg("success", ` | subagents ${subagentTps} t/s (${subagentSummaries.length} call${subagentSummaries.length === 1 ? "" : "s"})`)
|
|
268
|
-
: "";
|
|
269
|
-
|
|
270
|
-
ctx.ui.notify(`${icon} total ${tpsLabel}${mainDetail}${subagentDetail}`, "info");
|
|
271
|
-
const finalMainStatus = mainTps > 0 ? `main ${mainTps} t/s` : "main N/A";
|
|
272
|
-
const finalSubagentStatus = totalSubagentTokens > 0 && totalSubagentMs > 0
|
|
273
|
-
? `subagents ${Math.round(totalSubagentTokens / (totalSubagentMs / 1000))} t/s`
|
|
274
|
-
: "";
|
|
275
|
-
ctx.ui.setStatus(
|
|
276
|
-
"tps",
|
|
277
|
-
`${theme.fg("accent", `done ${tps} t/s`)} ${theme.fg("success", `(${[finalMainStatus, finalSubagentStatus].filter(Boolean).join(" ")})`)}`,
|
|
278
|
-
);
|
|
279
|
-
});
|
|
280
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// ponytail: task-specific validators enforce the acceptance/verification
|
|
2
|
-
// contract used only by task mode. Pure string functions; no fs/pi.
|
|
3
|
-
|
|
4
|
-
import { markerValidator } from "./validators.ts";
|
|
5
|
-
|
|
6
|
-
const PLAN_READY = markerValidator("WORKFLOW_PLAN_STATUS", "ready");
|
|
7
|
-
const REVIEW_CLEAN = markerValidator("WORKFLOW_REVIEW_STATUS", "clean");
|
|
8
|
-
|
|
9
|
-
const AC_ID_RE = /\bAC-(\d+)\b/gi;
|
|
10
|
-
const ACCEPTANCE_CRITERIA_HEADING_RE = /^##\s+Acceptance Criteria\b/im;
|
|
11
|
-
const IMPLEMENTATION_HEADING_RE = /^##\s+Implementation Outline\b/im;
|
|
12
|
-
const VERIFICATION_RE = /\bVerification\s*:/gi;
|
|
13
|
-
|
|
14
|
-
function extractACIds(text: string): string[] {
|
|
15
|
-
const ids: string[] = [];
|
|
16
|
-
let m: RegExpExecArray | null;
|
|
17
|
-
AC_ID_RE.lastIndex = 0;
|
|
18
|
-
while ((m = AC_ID_RE.exec(text)) !== null) {
|
|
19
|
-
ids.push(m[0]!.toUpperCase());
|
|
20
|
-
}
|
|
21
|
-
return ids;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const taskPlanValidator = (content: string) => {
|
|
25
|
-
const marker = PLAN_READY(content);
|
|
26
|
-
if (!marker.ok) return marker;
|
|
27
|
-
|
|
28
|
-
if (!ACCEPTANCE_CRITERIA_HEADING_RE.test(content)) {
|
|
29
|
-
return { ok: false as const, reason: "missing `## Acceptance Criteria` section" };
|
|
30
|
-
}
|
|
31
|
-
const ids = extractACIds(content);
|
|
32
|
-
if (ids.length === 0) {
|
|
33
|
-
return { ok: false as const, reason: "acceptance criteria section must contain at least one AC-<n> ID" };
|
|
34
|
-
}
|
|
35
|
-
const unique = new Set(ids);
|
|
36
|
-
if (unique.size !== ids.length) {
|
|
37
|
-
return { ok: false as const, reason: "acceptance criteria AC-<n> IDs must be unique" };
|
|
38
|
-
}
|
|
39
|
-
if (!IMPLEMENTATION_HEADING_RE.test(content)) {
|
|
40
|
-
return { ok: false as const, reason: "missing `## Implementation Outline` section" };
|
|
41
|
-
}
|
|
42
|
-
const verifications = (content.match(VERIFICATION_RE) ?? []).length;
|
|
43
|
-
if (verifications < unique.size) {
|
|
44
|
-
return { ok: false as const, reason: "each AC-<n> must have a Verification: step" };
|
|
45
|
-
}
|
|
46
|
-
return { ok: true as const };
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export function taskReviewValidator(
|
|
50
|
-
planContent: string | undefined,
|
|
51
|
-
reviewText: string,
|
|
52
|
-
): { ok: true } | { ok: false; reason: string } {
|
|
53
|
-
const marker = REVIEW_CLEAN(reviewText);
|
|
54
|
-
if (!marker.ok) return marker;
|
|
55
|
-
|
|
56
|
-
const planIds = extractACIds(planContent ?? "");
|
|
57
|
-
if (planIds.length === 0) {
|
|
58
|
-
return { ok: false as const, reason: "plan contains no AC-<n> criteria; cannot verify coverage" };
|
|
59
|
-
}
|
|
60
|
-
const reviewIds = extractACIds(reviewText);
|
|
61
|
-
const missing = planIds.filter((id) => !reviewIds.includes(id));
|
|
62
|
-
if (missing.length > 0) {
|
|
63
|
-
return { ok: false as const, reason: `review is missing coverage for ${missing.join(", ")}` };
|
|
64
|
-
}
|
|
65
|
-
if (!/\bpassed\b/i.test(reviewText) || !/\bverification\b/i.test(reviewText)) {
|
|
66
|
-
return { ok: false as const, reason: "review must report that verification passed" };
|
|
67
|
-
}
|
|
68
|
-
return { ok: true as const };
|
|
69
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: selesai-default
|
|
3
|
-
description: Default Selesai skill. A minimal example showing how bundled skills are shipped in src/skills/ and loaded at boot via additionalSkillPaths. Safe to delete or replace.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Selesai Default Skill
|
|
7
|
-
|
|
8
|
-
This skill ships inside `@selesai/code` under `src/skills/` and is loaded
|
|
9
|
-
automatically at boot (same hook as `--skill`), so every Selesai install gets
|
|
10
|
-
it without users copying anything to `~/.selesai/agent/skills/`.
|
|
11
|
-
|
|
12
|
-
Add more bundled skills by creating sibling directories here, each with its own
|
|
13
|
-
`SKILL.md` (YAML frontmatter `name` + `description`, then the body). The build
|
|
14
|
-
copies the whole `src/skills/` tree into `dist/skills/`.
|
|
15
|
-
|
|
16
|
-
To disable all skills at runtime: `selesai --no-skills`.
|