poe-code 3.0.258 → 3.0.259-beta.1

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.
Files changed (76) hide show
  1. package/dist/cli/commands/gaslight.js +84 -1
  2. package/dist/cli/commands/gaslight.js.map +1 -1
  3. package/dist/cli/poe-theme.d.ts +1 -0
  4. package/dist/cli/poe-theme.js +5 -0
  5. package/dist/cli/poe-theme.js.map +1 -0
  6. package/dist/cli/program.js +1 -0
  7. package/dist/cli/program.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +2753 -1123
  10. package/dist/index.js.map +4 -4
  11. package/dist/metafile.json +1 -1
  12. package/dist/sdk/gaslight.d.ts +1 -1
  13. package/dist/sdk/gaslight.js +1 -1
  14. package/dist/sdk/gaslight.js.map +1 -1
  15. package/package.json +3 -1
  16. package/packages/agent-gaslight/dist/config.d.ts +5 -1
  17. package/packages/agent-gaslight/dist/config.js +32 -11
  18. package/packages/agent-gaslight/dist/index.d.ts +3 -2
  19. package/packages/agent-gaslight/dist/index.js +2 -1
  20. package/packages/agent-gaslight/dist/ingest.d.ts +2 -0
  21. package/packages/agent-gaslight/dist/ingest.js +486 -0
  22. package/packages/agent-gaslight/dist/run.js +1 -1
  23. package/packages/agent-gaslight/dist/types.d.ts +41 -6
  24. package/packages/agent-harness/dist/loader/run.js +6 -21
  25. package/packages/agent-script/dist/cli.d.ts +2 -3
  26. package/packages/agent-script/dist/cli.js +70 -36
  27. package/packages/agent-script/dist/example-runner.d.ts +2 -3
  28. package/packages/agent-script/dist/example-runner.js +69 -56
  29. package/packages/agent-script/dist/interp/exceptions.js +1 -1
  30. package/packages/agent-script/dist/interp/globals/object-array.d.ts +1 -1
  31. package/packages/agent-script/dist/interp/globals/object-array.js +39 -20
  32. package/packages/agent-script/dist/interp/host-bridge.js +1 -1
  33. package/packages/agent-script/dist/interp/interpreter.js +83 -17
  34. package/packages/agent-script/dist/interp/methods/array.js +25 -2
  35. package/packages/agent-script/dist/interp/methods/regex.js +1 -1
  36. package/packages/agent-script/dist/interp/promise-tracker.d.ts +16 -0
  37. package/packages/agent-script/dist/interp/promise-tracker.js +58 -0
  38. package/packages/agent-script/dist/interp/promise.js +38 -7
  39. package/packages/agent-script/dist/interp/scope.d.ts +1 -0
  40. package/packages/agent-script/dist/interp/scope.js +3 -0
  41. package/packages/agent-script/dist/interp/values.js +2 -0
  42. package/packages/agent-script/dist/lint/index.d.ts +2 -0
  43. package/packages/agent-script/dist/lint/index.js +2 -0
  44. package/packages/agent-script/dist/lint/rules/AS-export-import-meta.d.ts +6 -1
  45. package/packages/agent-script/dist/lint/rules/AS-export-import-meta.js +33 -4
  46. package/packages/agent-script/dist/modules/agent.js +10 -1
  47. package/packages/agent-script/dist/modules/log.js +5 -1
  48. package/packages/agent-script/dist/modules/registry.js +9 -3
  49. package/packages/agent-script/dist/output-stream.d.ts +12 -0
  50. package/packages/agent-script/dist/output-stream.js +50 -0
  51. package/packages/agent-script/dist/parse/parser.d.ts +1 -1
  52. package/packages/agent-script/dist/parse/parser.js +151 -45
  53. package/packages/agent-script/dist/parse/tokenizer.js +26 -3
  54. package/packages/agent-script/dist/run.js +14 -3
  55. package/packages/agent-script/dist/runner/run-harness.js +28 -5
  56. package/packages/agent-traces/dist/collect.d.ts +4 -0
  57. package/packages/agent-traces/dist/collect.js +102 -0
  58. package/packages/agent-traces/dist/index.d.ts +4 -0
  59. package/packages/agent-traces/dist/index.js +3 -0
  60. package/packages/agent-traces/dist/jsonl.d.ts +2 -0
  61. package/packages/agent-traces/dist/jsonl.js +7 -0
  62. package/packages/agent-traces/dist/line-json.d.ts +4 -0
  63. package/packages/agent-traces/dist/line-json.js +40 -0
  64. package/packages/agent-traces/dist/readers/claude.d.ts +2 -0
  65. package/packages/agent-traces/dist/readers/claude.js +192 -0
  66. package/packages/agent-traces/dist/readers/codex.d.ts +2 -0
  67. package/packages/agent-traces/dist/readers/codex.js +266 -0
  68. package/packages/agent-traces/dist/readers/index.d.ts +5 -0
  69. package/packages/agent-traces/dist/readers/index.js +4 -0
  70. package/packages/agent-traces/dist/types.d.ts +84 -0
  71. package/packages/agent-traces/dist/types.js +1 -0
  72. package/packages/package-lint/dist/model.js +5 -1
  73. package/packages/package-lint/dist/source-imports.d.ts +11 -1
  74. package/packages/package-lint/dist/source-imports.js +30 -4
  75. package/packages/tiny-stdio-mcp-test-server/dist/cli.js +41 -0
  76. package/packages/tiny-stdio-mcp-test-server/dist/index.js +8 -0
@@ -1 +1 @@
1
- export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig, runGaslight, type GaslightConfig, type GaslightEvent, type GaslightFileSystem, type GaslightOptions, type GaslightResult, type GaslightRound, type GaslightSpawn } from "../../packages/agent-gaslight/dist/index.js";
1
+ export { GASLIGHT_CONFIG_EXAMPLE, ingestGaslight, loadGaslightConfig, parseGaslightConfig, runGaslight, type GaslightConfig, type GaslightCollectHumanPrompts, type GaslightEvent, type GaslightFileSystem, type GaslightIngestEvent, type GaslightIngestOptions, type GaslightIngestResult, type GaslightOptions, type GaslightResult, type GaslightRound, type GaslightSpawn } from "../../packages/agent-gaslight/dist/index.js";
@@ -1,2 +1,2 @@
1
- export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig, runGaslight } from "@poe-code/agent-gaslight";
1
+ export { GASLIGHT_CONFIG_EXAMPLE, ingestGaslight, loadGaslightConfig, parseGaslightConfig, runGaslight } from "@poe-code/agent-gaslight";
2
2
  //# sourceMappingURL=gaslight.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gaslight.js","sourceRoot":"","sources":["../../src/sdk/gaslight.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,EAQZ,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"gaslight.js","sourceRoot":"","sources":["../../src/sdk/gaslight.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EAYZ,MAAM,0BAA0B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "3.0.258",
3
+ "version": "3.0.259-beta.1",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -116,6 +116,7 @@
116
116
  "packages/agent-harness/dist",
117
117
  "packages/agent-harness-tools/dist",
118
118
  "packages/agent-gaslight/dist",
119
+ "packages/agent-traces/dist",
119
120
  "packages/agent-hook-config/dist",
120
121
  "packages/agent-human-in-loop/dist",
121
122
  "packages/agent-maestro/dist",
@@ -213,6 +214,7 @@
213
214
  "@poe-code/agent-script": "*",
214
215
  "@poe-code/agent-skill-config": "*",
215
216
  "@poe-code/agent-spawn": "*",
217
+ "@poe-code/agent-traces": "*",
216
218
  "@poe-code/braintrust": "*",
217
219
  "@poe-code/cached-resource": "*",
218
220
  "@poe-code/config-extends": "*",
@@ -1,3 +1,7 @@
1
1
  import type { GaslightConfig, GaslightFileSystem } from "./types.js";
2
2
  export declare const GASLIGHT_CONFIG_EXAMPLE: string;
3
- export declare function loadGaslightConfig(cwd: string, homeDir: string, fs?: GaslightFileSystem): Promise<GaslightConfig>;
3
+ export interface ParseGaslightConfigOptions {
4
+ rejectExtraKeys?: boolean;
5
+ }
6
+ export declare function parseGaslightConfig(content: string, configPath: string, options?: ParseGaslightConfigOptions): Omit<GaslightConfig, "path">;
7
+ export declare function loadGaslightConfig(cwd: string, homeDir: string, fs?: GaslightFileSystem, configPath?: string): Promise<GaslightConfig>;
@@ -14,11 +14,31 @@ function isMissingFile(error) {
14
14
  "code" in error &&
15
15
  error.code === "ENOENT");
16
16
  }
17
- function validateConfig(value, configPath) {
17
+ function objectKeys(value) {
18
+ return Object.keys(value).sort();
19
+ }
20
+ export function parseGaslightConfig(content, configPath, options = {}) {
21
+ let parsed;
22
+ try {
23
+ parsed = parse(content);
24
+ }
25
+ catch (error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ throw new Error(`Invalid gaslight config at ${configPath}: ${message}`, { cause: error });
28
+ }
29
+ return validateConfig(parsed, configPath, options);
30
+ }
31
+ function validateConfig(value, configPath, options) {
18
32
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
19
33
  throw new Error(`Invalid gaslight config at ${configPath}: expected a YAML object.`);
20
34
  }
21
35
  const config = value;
36
+ if (options.rejectExtraKeys) {
37
+ const extraKey = objectKeys(config).find((key) => key !== "prompt" && key !== "followups");
38
+ if (extraKey) {
39
+ throw new Error(`Invalid gaslight config at ${configPath}: unexpected key "${extraKey}".`);
40
+ }
41
+ }
22
42
  if (typeof config.prompt !== "string" || config.prompt.trim().length === 0) {
23
43
  throw new Error(`Invalid gaslight config at ${configPath}: prompt must be a non-empty string.`);
24
44
  }
@@ -32,7 +52,16 @@ function validateConfig(value, configPath) {
32
52
  followups: config.followups.map((followup) => followup.trim())
33
53
  };
34
54
  }
35
- export async function loadGaslightConfig(cwd, homeDir, fs = nodeFs) {
55
+ export async function loadGaslightConfig(cwd, homeDir, fs = nodeFs, configPath) {
56
+ if (configPath) {
57
+ const absoluteConfigPath = path.isAbsolute(configPath)
58
+ ? configPath
59
+ : path.join(cwd, configPath);
60
+ return {
61
+ ...parseGaslightConfig(await fs.readFile(absoluteConfigPath, "utf8"), absoluteConfigPath),
62
+ path: absoluteConfigPath
63
+ };
64
+ }
36
65
  const paths = [
37
66
  path.join(cwd, ".poe-code", "gaslight.yaml"),
38
67
  path.join(homeDir, ".poe-code", "gaslight.yaml")
@@ -48,15 +77,7 @@ export async function loadGaslightConfig(cwd, homeDir, fs = nodeFs) {
48
77
  }
49
78
  throw error;
50
79
  }
51
- let parsed;
52
- try {
53
- parsed = parse(content);
54
- }
55
- catch (error) {
56
- const message = error instanceof Error ? error.message : String(error);
57
- throw new Error(`Invalid gaslight config at ${configPath}: ${message}`, { cause: error });
58
- }
59
- return { ...validateConfig(parsed, configPath), path: configPath };
80
+ return { ...parseGaslightConfig(content, configPath), path: configPath };
60
81
  }
61
82
  throw new Error(`No gaslight config found. Searched:\n- ${paths[0]}\n- ${paths[1]}\n\nCreate one with:\n${GASLIGHT_CONFIG_EXAMPLE}`);
62
83
  }
@@ -1,3 +1,4 @@
1
- export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig } from "./config.js";
1
+ export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig, parseGaslightConfig } from "./config.js";
2
+ export { ingestGaslight } from "./ingest.js";
2
3
  export { runGaslight } from "./run.js";
3
- export type { GaslightConfig, GaslightEvent, GaslightFileSystem, GaslightOptions, GaslightResult, GaslightRound, GaslightSpawn } from "./types.js";
4
+ export type { GaslightConfig, GaslightCollectHumanPrompts, GaslightEvent, GaslightFileSystem, GaslightIngestEvent, GaslightIngestOptions, GaslightIngestResult, GaslightOptions, GaslightResult, GaslightRound, GaslightSpawn } from "./types.js";
@@ -1,2 +1,3 @@
1
- export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig } from "./config.js";
1
+ export { GASLIGHT_CONFIG_EXAMPLE, loadGaslightConfig, parseGaslightConfig } from "./config.js";
2
+ export { ingestGaslight } from "./ingest.js";
2
3
  export { runGaslight } from "./run.js";
@@ -0,0 +1,2 @@
1
+ import type { GaslightIngestOptions, GaslightIngestResult } from "./types.js";
2
+ export declare function ingestGaslight(options: GaslightIngestOptions): Promise<GaslightIngestResult>;
@@ -0,0 +1,486 @@
1
+ import { promises as nodeFs } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import parseDuration from "parse-duration";
5
+ import { collectHumanPromptsWithStats } from "@poe-code/agent-traces";
6
+ import { spawn as defaultSpawn } from "@poe-code/agent-spawn";
7
+ import { parseGaslightConfig } from "./config.js";
8
+ function isMissingFile(error) {
9
+ return (typeof error === "object" &&
10
+ error !== null &&
11
+ "code" in error &&
12
+ error.code === "ENOENT");
13
+ }
14
+ async function exists(fs, filePath) {
15
+ try {
16
+ return (await fs.stat(filePath)).isFile();
17
+ }
18
+ catch (error) {
19
+ if (isMissingFile(error)) {
20
+ return false;
21
+ }
22
+ throw error;
23
+ }
24
+ }
25
+ function sanitizeAgentForFileName(agent) {
26
+ const parts = [];
27
+ let previousWasDash = false;
28
+ for (const char of agent.toLowerCase()) {
29
+ const code = char.charCodeAt(0);
30
+ const isLowercaseLetter = code >= 97 && code <= 122;
31
+ const isNumber = code >= 48 && code <= 57;
32
+ if (isLowercaseLetter || isNumber) {
33
+ parts.push(char);
34
+ previousWasDash = false;
35
+ continue;
36
+ }
37
+ if (!previousWasDash) {
38
+ parts.push("-");
39
+ previousWasDash = true;
40
+ }
41
+ }
42
+ while (parts[0] === "-") {
43
+ parts.shift();
44
+ }
45
+ while (parts[parts.length - 1] === "-") {
46
+ parts.pop();
47
+ }
48
+ return parts.join("") || "agent";
49
+ }
50
+ function resolvePath(cwd, filePath) {
51
+ return path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
52
+ }
53
+ async function resolveOutputPath(fs, cwd, analysisAgent, outputPath) {
54
+ if (outputPath) {
55
+ const absolutePath = resolvePath(cwd, outputPath);
56
+ return { absolutePath, resultPath: outputPath };
57
+ }
58
+ const configDirectory = path.join(cwd, ".poe-code");
59
+ const defaultPath = path.join(configDirectory, "gaslight.yaml");
60
+ if (!(await exists(fs, defaultPath))) {
61
+ return { absolutePath: defaultPath, resultPath: ".poe-code/gaslight.yaml" };
62
+ }
63
+ const prefix = sanitizeAgentForFileName(analysisAgent);
64
+ let index = 1;
65
+ while (true) {
66
+ const basename = index === 1 ? `${prefix}-gaslight.yaml` : `${prefix}-gaslight-${index}.yaml`;
67
+ const candidate = path.join(configDirectory, basename);
68
+ if (!(await exists(fs, candidate))) {
69
+ return { absolutePath: candidate, resultPath: path.join(".poe-code", basename) };
70
+ }
71
+ index += 1;
72
+ }
73
+ }
74
+ function resolveSince(value) {
75
+ if (value === undefined) {
76
+ const milliseconds = parseDuration("30d");
77
+ return milliseconds === null ? undefined : new Date(Date.now() - milliseconds);
78
+ }
79
+ if (value instanceof Date) {
80
+ return value;
81
+ }
82
+ const milliseconds = parseDuration(value);
83
+ if (milliseconds === null || milliseconds <= 0) {
84
+ throw new Error(`Invalid since duration "${value}".`);
85
+ }
86
+ return new Date(Date.now() - milliseconds);
87
+ }
88
+ async function resolveDataPath(cwd, keepDataPath) {
89
+ if (keepDataPath) {
90
+ const absolutePath = resolvePath(cwd, keepDataPath);
91
+ return { absolutePath, resultPath: keepDataPath };
92
+ }
93
+ const resultPath = path.join(".poe-code", "ingest", `human-prompts-${process.pid}-${Date.now()}-${process.hrtime.bigint()}.md`);
94
+ return { absolutePath: path.join(cwd, resultPath), resultPath };
95
+ }
96
+ function buildAnalysisPrompt(dataPath) {
97
+ return [
98
+ "Read this curated Markdown file of human prompts from coding-agent traces:",
99
+ dataPath,
100
+ "",
101
+ "Generate a gaslight.yaml file that captures recurring follow-up prompts the human uses after agent work.",
102
+ "Be clever: infer workflow-level prompts from the evidence instead of copying frequent strings.",
103
+ "Return only YAML with this exact shape:",
104
+ "prompt: <string>",
105
+ "followups:",
106
+ " - <string>",
107
+ "",
108
+ "Rules:",
109
+ "- Prefer concise followups that generalize across tasks.",
110
+ "- Do not produce two followups for the same workflow step; merge semantic duplicates.",
111
+ "- Repeated short prompts like \"commit\" are evidence for one well-placed workflow check, not multiple followups.",
112
+ "- Order followups as a useful review sequence: quality, verification, cleanup, then commit or release when supported by the evidence.",
113
+ "- Do not include project secrets, file paths, names, tokens, or one-off task details.",
114
+ "- Preserve the user's direct style when it is reusable.",
115
+ "- Use 3 to 8 followups."
116
+ ].join("\n");
117
+ }
118
+ function replaceEvery(value, search, replacement) {
119
+ if (search.length === 0) {
120
+ return value;
121
+ }
122
+ return value.split(search).join(replacement);
123
+ }
124
+ function redactKnownPaths(value, cwd, homeDir) {
125
+ const replacements = [
126
+ { value: cwd, label: "<workspace>" },
127
+ { value: homeDir, label: "<home>" }
128
+ ].sort((left, right) => right.value.length - left.value.length);
129
+ let redacted = value;
130
+ for (const replacement of replacements) {
131
+ redacted = replaceEvery(redacted, replacement.value, replacement.label);
132
+ }
133
+ return redacted;
134
+ }
135
+ function stripIdeSelection(value) {
136
+ let stripped = value;
137
+ const openTag = "<ide_selection>";
138
+ const closeTag = "</ide_selection>";
139
+ while (true) {
140
+ const startIndex = stripped.indexOf(openTag);
141
+ if (startIndex === -1) {
142
+ return stripped;
143
+ }
144
+ const endIndex = stripped.indexOf(closeTag, startIndex + openTag.length);
145
+ if (endIndex === -1) {
146
+ return stripped;
147
+ }
148
+ stripped =
149
+ stripped.slice(0, startIndex) + stripped.slice(endIndex + closeTag.length);
150
+ }
151
+ }
152
+ function normalizePromptText(value, cwd, homeDir) {
153
+ const normalizedLineEndings = stripIdeSelection(value)
154
+ .split("\r\n")
155
+ .join("\n")
156
+ .split("\r")
157
+ .join("\n");
158
+ const lines = [];
159
+ let blankLines = 0;
160
+ for (const line of normalizedLineEndings.split("\n")) {
161
+ const trimmedRight = line.trimEnd();
162
+ if (trimmedRight.trim().length === 0) {
163
+ blankLines += 1;
164
+ if (blankLines <= 1) {
165
+ lines.push("");
166
+ }
167
+ continue;
168
+ }
169
+ blankLines = 0;
170
+ lines.push(trimmedRight);
171
+ }
172
+ return redactKnownPaths(lines.join("\n").trim(), cwd, homeDir);
173
+ }
174
+ function truncateForAnalysis(value, maxLength) {
175
+ if (value.length <= maxLength) {
176
+ return value;
177
+ }
178
+ const preferredCut = value.lastIndexOf("\n", maxLength);
179
+ const minimumUsefulCut = Math.floor(maxLength * 0.65);
180
+ const cutIndex = preferredCut >= minimumUsefulCut ? preferredCut : maxLength;
181
+ return `${value.slice(0, cutIndex).trimEnd()}\n[truncated]`;
182
+ }
183
+ function markdownBlock(value) {
184
+ return value
185
+ .split("\n")
186
+ .map((line) => ` ${line}`)
187
+ .join("\n");
188
+ }
189
+ function markdownInline(value) {
190
+ return value.split("\n").join(" ").split("`").join("'");
191
+ }
192
+ function groupPromptsByTrace(records) {
193
+ const groups = new Map();
194
+ for (const record of records) {
195
+ const key = `${record.source}:${record.traceId}`;
196
+ const existing = groups.get(key);
197
+ if (existing) {
198
+ existing.prompts.push(record);
199
+ if (!existing.title && record.title) {
200
+ existing.title = record.title;
201
+ }
202
+ continue;
203
+ }
204
+ groups.set(key, {
205
+ traceId: record.traceId,
206
+ source: record.source,
207
+ ...(record.title ? { title: record.title } : {}),
208
+ prompts: [record]
209
+ });
210
+ }
211
+ return [...groups.values()];
212
+ }
213
+ function shortPromptKey(record, cwd, homeDir) {
214
+ const text = normalizePromptText(record.text, cwd, homeDir);
215
+ if (text.length === 0 || text.length > 80 || text.includes("\n")) {
216
+ return undefined;
217
+ }
218
+ return text.toLowerCase();
219
+ }
220
+ function buildRepeatedShortPromptSection(records, cwd, homeDir) {
221
+ const counts = new Map();
222
+ for (const record of records) {
223
+ const key = shortPromptKey(record, cwd, homeDir);
224
+ if (!key) {
225
+ continue;
226
+ }
227
+ counts.set(key, (counts.get(key) ?? 0) + 1);
228
+ }
229
+ const repeated = [...counts.entries()]
230
+ .filter((entry) => entry[1] > 1)
231
+ .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
232
+ .slice(0, 12);
233
+ if (repeated.length === 0) {
234
+ return [
235
+ "## Repeated short prompts",
236
+ "",
237
+ "No repeated short prompts were found.",
238
+ ""
239
+ ];
240
+ }
241
+ return [
242
+ "## Repeated short prompts",
243
+ "",
244
+ "Treat these as frequency evidence. Do not copy them blindly or create duplicate followups.",
245
+ "",
246
+ ...repeated.map(([text, count]) => `- \`${markdownInline(text)}\` - ${count} occurrences`),
247
+ ""
248
+ ];
249
+ }
250
+ function buildAnalysisInput(records, cwd, homeDir) {
251
+ const groups = groupPromptsByTrace(records);
252
+ const lines = [
253
+ "# Gaslight ingest analysis input",
254
+ "",
255
+ "This file is curated evidence for generating a reusable gaslight.yaml.",
256
+ "Infer durable follow-up behavior from the prompts. Do not copy task-specific text.",
257
+ "",
258
+ "## Summary",
259
+ "",
260
+ `- Prompts: ${records.length}`,
261
+ `- Traces: ${groups.length}`,
262
+ "",
263
+ ...buildRepeatedShortPromptSection(records, cwd, homeDir),
264
+ "## Prompt evidence",
265
+ ""
266
+ ];
267
+ groups.forEach((group, groupIndex) => {
268
+ const title = group.title
269
+ ? truncateForAnalysis(markdownInline(normalizePromptText(group.title, cwd, homeDir)), 90)
270
+ : group.traceId;
271
+ lines.push(`### Trace ${groupIndex + 1}: ${title}`);
272
+ lines.push("");
273
+ lines.push(`- Source: ${group.source}`);
274
+ lines.push(`- Prompts in trace: ${group.prompts.length}`);
275
+ lines.push("");
276
+ group.prompts.forEach((record, promptIndex) => {
277
+ const text = truncateForAnalysis(normalizePromptText(record.text, cwd, homeDir), 1200);
278
+ lines.push(`#### Prompt ${promptIndex + 1}`);
279
+ lines.push("");
280
+ if (record.timestamp) {
281
+ lines.push(`- Timestamp: ${record.timestamp}`);
282
+ lines.push("");
283
+ }
284
+ lines.push(markdownBlock(text));
285
+ lines.push("");
286
+ });
287
+ });
288
+ return `${lines.join("\n").trimEnd()}\n`;
289
+ }
290
+ async function writeAnalysisInput(fs, records, filePath, cwd, homeDir) {
291
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
292
+ await fs.writeFile(filePath, buildAnalysisInput(records, cwd, homeDir), { encoding: "utf8" });
293
+ }
294
+ async function removeAnalysisInput(fs, filePath) {
295
+ if (!fs.unlink) {
296
+ return;
297
+ }
298
+ try {
299
+ await fs.unlink(filePath);
300
+ }
301
+ catch (error) {
302
+ if (isMissingFile(error)) {
303
+ return;
304
+ }
305
+ throw error;
306
+ }
307
+ }
308
+ function stripMarkdownFence(value) {
309
+ const lines = value.trim().split("\n");
310
+ if (lines.length >= 2 &&
311
+ lines[0]?.startsWith("```") &&
312
+ lines[lines.length - 1]?.startsWith("```")) {
313
+ return lines.slice(1, -1).join("\n").trim();
314
+ }
315
+ return value.trim();
316
+ }
317
+ function extractFencedYaml(value) {
318
+ const match = value.match(/```(?:yaml|yml)?\s*\n([\s\S]*?)\n```/i);
319
+ return match?.[1]?.trim();
320
+ }
321
+ function extractPromptSection(value) {
322
+ const lines = value.trim().split("\n");
323
+ const startIndex = lines.findIndex((line) => /^\s*prompt\s*:/.test(line));
324
+ if (startIndex === -1) {
325
+ return undefined;
326
+ }
327
+ const selected = [];
328
+ let sawFollowups = false;
329
+ for (const line of lines.slice(startIndex)) {
330
+ if (/^\s*followups\s*:/.test(line)) {
331
+ sawFollowups = true;
332
+ selected.push(line);
333
+ continue;
334
+ }
335
+ if (sawFollowups &&
336
+ line.trim().length > 0 &&
337
+ !line.startsWith(" ") &&
338
+ !line.startsWith("-") &&
339
+ !/^\s*-\s+/.test(line)) {
340
+ break;
341
+ }
342
+ selected.push(line);
343
+ }
344
+ return selected.join("\n").trim();
345
+ }
346
+ function extractYamlCandidate(value) {
347
+ const trimmed = value.trim();
348
+ return extractFencedYaml(trimmed) ?? extractPromptSection(trimmed) ?? stripMarkdownFence(trimmed);
349
+ }
350
+ function asRecord(value) {
351
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
352
+ return undefined;
353
+ }
354
+ return value;
355
+ }
356
+ function extractTextBlocks(value) {
357
+ if (typeof value === "string") {
358
+ return [value];
359
+ }
360
+ if (!Array.isArray(value)) {
361
+ return [];
362
+ }
363
+ const blocks = [];
364
+ for (const item of value) {
365
+ if (typeof item === "string") {
366
+ blocks.push(item);
367
+ continue;
368
+ }
369
+ const record = asRecord(item);
370
+ if (record?.type === "text" &&
371
+ typeof record.text === "string" &&
372
+ record.text.trim().length > 0) {
373
+ blocks.push(record.text);
374
+ }
375
+ }
376
+ return blocks;
377
+ }
378
+ function extractAgentMessagesFromJsonl(value) {
379
+ const messages = [];
380
+ for (const rawLine of value.split("\n")) {
381
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1).trim() : rawLine.trim();
382
+ if (line.length === 0) {
383
+ continue;
384
+ }
385
+ let parsed;
386
+ try {
387
+ parsed = JSON.parse(line);
388
+ }
389
+ catch {
390
+ return undefined;
391
+ }
392
+ const record = asRecord(parsed);
393
+ const item = asRecord(record?.item);
394
+ if (record?.type === "item.completed" &&
395
+ item?.type === "agent_message" &&
396
+ typeof item.text === "string" &&
397
+ item.text.trim().length > 0) {
398
+ messages.push(item.text.trim());
399
+ continue;
400
+ }
401
+ const message = asRecord(record?.message);
402
+ if (record?.type === "assistant" && message) {
403
+ for (const text of extractTextBlocks(message.content)) {
404
+ if (text.trim().length > 0) {
405
+ messages.push(text.trim());
406
+ }
407
+ }
408
+ }
409
+ }
410
+ return messages.length === 0 ? undefined : messages.join("\n");
411
+ }
412
+ function extractGeneratedConfigContent(stdout) {
413
+ return extractAgentMessagesFromJsonl(stdout) ?? stdout;
414
+ }
415
+ async function writeGeneratedConfig(fs, content, absoluteOutputPath) {
416
+ const yaml = extractYamlCandidate(extractGeneratedConfigContent(content));
417
+ parseGaslightConfig(yaml, "generated gaslight config", { rejectExtraKeys: true });
418
+ await fs.mkdir(path.dirname(absoluteOutputPath), { recursive: true });
419
+ const temporaryPath = `${absoluteOutputPath}.tmp-${process.pid}-${Date.now()}`;
420
+ await fs.writeFile(temporaryPath, `${yaml}\n`, { encoding: "utf8" });
421
+ if (fs.rename) {
422
+ await fs.rename(temporaryPath, absoluteOutputPath);
423
+ return;
424
+ }
425
+ await fs.writeFile(absoluteOutputPath, `${yaml}\n`, { encoding: "utf8" });
426
+ }
427
+ export async function ingestGaslight(options) {
428
+ const cwd = options.cwd ?? process.cwd();
429
+ const homeDir = options.homeDir ?? os.homedir();
430
+ const fs = (options.fs ?? nodeFs);
431
+ const spawn = options.spawn ?? defaultSpawn;
432
+ const collectHumanPrompts = options.collectHumanPrompts ?? collectHumanPromptsWithStats;
433
+ const since = resolveSince(options.since);
434
+ const collection = await collectHumanPrompts({
435
+ sources: options.sources,
436
+ cwd,
437
+ homeDir,
438
+ since,
439
+ limit: options.limit ?? 200,
440
+ allWorkspaces: options.allWorkspaces,
441
+ fs
442
+ });
443
+ options.onEvent?.({ type: "traces.discovered", count: collection.traceCount });
444
+ options.onEvent?.({
445
+ type: "prompts.extracted",
446
+ traces: collection.traceCount,
447
+ prompts: collection.records.length
448
+ });
449
+ if (collection.records.length === 0) {
450
+ throw new Error("No human prompts found in selected traces.");
451
+ }
452
+ const dataPath = await resolveDataPath(cwd, options.keepDataPath);
453
+ await writeAnalysisInput(fs, collection.records, dataPath.absolutePath, cwd, homeDir);
454
+ const shouldRemoveAnalysisInput = options.keepDataPath === undefined;
455
+ try {
456
+ options.onEvent?.({
457
+ type: "analysis.started",
458
+ agent: options.analysisAgent,
459
+ dataPath: dataPath.absolutePath
460
+ });
461
+ const result = await spawn(options.analysisAgent, {
462
+ prompt: buildAnalysisPrompt(dataPath.absolutePath),
463
+ cwd,
464
+ mode: "read",
465
+ ...(options.model ? { model: options.model } : {})
466
+ });
467
+ if (result.exitCode !== 0) {
468
+ const message = result.stderr.trim() || result.stdout.trim() || `exit code ${result.exitCode}`;
469
+ throw new Error(`Gaslight ingest analysis failed: ${message}`);
470
+ }
471
+ const outputPath = await resolveOutputPath(fs, cwd, options.analysisAgent, options.outputPath);
472
+ await writeGeneratedConfig(fs, result.stdout, outputPath.absolutePath);
473
+ options.onEvent?.({ type: "config.written", path: outputPath.resultPath });
474
+ return {
475
+ outputPath: outputPath.resultPath,
476
+ dataPath: dataPath.resultPath,
477
+ promptCount: collection.records.length,
478
+ traceCount: collection.traceCount
479
+ };
480
+ }
481
+ finally {
482
+ if (shouldRemoveAnalysisInput) {
483
+ await removeAnalysisInput(fs, dataPath.absolutePath);
484
+ }
485
+ }
486
+ }
@@ -54,7 +54,7 @@ export async function runGaslight(options) {
54
54
  await requirePlan(fs, cwd, options.planPath);
55
55
  const config = options.prompt !== undefined && options.followups !== undefined
56
56
  ? { prompt: options.prompt.trim(), followups: options.followups.map((value) => value.trim()) }
57
- : await loadGaslightConfig(cwd, homeDir, fs);
57
+ : await loadGaslightConfig(cwd, homeDir, fs, options.configPath);
58
58
  const prompts = [`${config.prompt} ${options.planPath}`, ...config.followups];
59
59
  const rounds = [];
60
60
  let usage;