executant 1.4.1 → 1.4.3

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/dist/index.js CHANGED
@@ -308,9 +308,10 @@ import { zodToJsonSchema } from "zod-to-json-schema";
308
308
 
309
309
  // src/lib/utils.ts
310
310
  import { readFileSync as readFileSync2 } from "node:fs";
311
- import { dirname, join } from "node:path";
311
+ import { basename, dirname, join } from "node:path";
312
312
  import { fileURLToPath } from "node:url";
313
- var PROMPTS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "prompts");
313
+ var __dir = dirname(fileURLToPath(import.meta.url));
314
+ var PROMPTS_DIR = basename(__dir) === "lib" ? join(__dir, "..", "prompts") : join(__dir, "prompts");
314
315
  function stripPromptHeader(raw) {
315
316
  return raw.replace(/^(#[^\n]*\n)+\n?/, "").trim();
316
317
  }
@@ -1291,6 +1292,21 @@ async function runPass3Judge(description, workflow2) {
1291
1292
  return { pass: true, feedback: "", skipped: true };
1292
1293
  }
1293
1294
  }
1295
+ function isNumericSequence(arr) {
1296
+ return arr.every((item, i) => item === String(i + 1));
1297
+ }
1298
+ function normalizeWorkflow(workflow2) {
1299
+ return {
1300
+ ...workflow2,
1301
+ steps: workflow2.steps.map((step) => {
1302
+ if (Array.isArray(step.forEach) && isNumericSequence(step.forEach)) {
1303
+ const { forEach, ...rest } = step;
1304
+ return { ...rest, repeat: forEach.length };
1305
+ }
1306
+ return step;
1307
+ })
1308
+ };
1309
+ }
1294
1310
  async function* streamPlan(args) {
1295
1311
  const { description, taskFile } = args;
1296
1312
  const skipResearch = args.fast || isSimpleRequest(description);
@@ -1413,7 +1429,7 @@ ${issues}` };
1413
1429
  if (!judgeResult.pass) {
1414
1430
  yield { type: "plan:warn", message: `Judge rejected plan but retries exhausted: ${judgeResult.feedback}` };
1415
1431
  }
1416
- const { goal, vars, steps, ...rest } = zodResult.data;
1432
+ const { goal, vars, steps, ...rest } = normalizeWorkflow(zodResult.data);
1417
1433
  const ordered = { goal, ...vars && { vars }, steps, ...rest };
1418
1434
  const yamlContent = dumpYaml(ordered, {
1419
1435
  lineWidth: -1,
@@ -1970,7 +1986,7 @@ async function* withLogger(gen, logger2) {
1970
1986
 
1971
1987
  // src/retrospective.ts
1972
1988
  import { existsSync as existsSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
1973
- import { basename, dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
1989
+ import { basename as basename2, dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
1974
1990
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1975
1991
  import { spawnSync } from "node:child_process";
1976
1992
  import { load as parseYaml2 } from "js-yaml";
@@ -2030,7 +2046,7 @@ ${metrics}
2030
2046
  ${content}`;
2031
2047
  }).join("\n\n---\n\n");
2032
2048
  const originalYaml = readFileSync5(workflowFilePath, "utf8");
2033
- const taskName = basename(workflowFilePath, ".yaml");
2049
+ const taskName = basename2(workflowFilePath, ".yaml");
2034
2050
  const prompt = RETROSPECTIVE_PROMPT.replaceAll("{{TASK_NAME}}", taskName).replaceAll("{{ORIGINAL_GOAL}}", workflow2.goal).replaceAll("{{ORIGINAL_YAML}}", originalYaml).replaceAll("{{HIGHLIGHTS}}", highlightContents).replaceAll("{{METRICS}}", metrics);
2035
2051
  const result = spawnSync(
2036
2052
  "claude",
@@ -97,6 +97,11 @@ Before finalising your JSON, scan every `prompt` and `command` field you wrote.
97
97
  For each field, ask: "Does this contain a file path or directory path as a string literal?"
98
98
  If yes, extract it to `vars` and replace with `{{var_name}}`.
99
99
 
100
+ **Pre-Output Self-Review — Repeat (MANDATORY):**
101
+ Scan every `forEach` field you wrote.
102
+ Ask: "Is this array just sequential numbers like `["1","2","3"]` with no meaningful items?"
103
+ If yes, replace the entire `forEach` with `repeat: N` where N is the count. Sequential-number forEach arrays are ALWAYS wrong — they are a misuse of forEach and must be converted to `repeat: N`.
104
+
100
105
  ## When to Use Each Step Type
101
106
 
102
107
  **Use `prompt` steps (AI-assisted) for:**
@@ -59,6 +59,13 @@ Are all file paths declared in `vars`?
59
59
  - Do any `prompt` or `command` fields contain hardcoded file paths or directory paths?
60
60
  - Hardcoded paths in steps (not in `vars`) are a violation
61
61
 
62
+ ### 5. Repeat Misuse (if applicable)
63
+ If the user's goal mentions "N times", "repeat N", "N iterations", or "N passes":
64
+
65
+ - Does any step use `forEach` with a sequential numeric array like `["1","2","3","4","5"]`?
66
+ - This is always wrong — `repeat: N` must be used instead of a numeric forEach array
67
+ - Reject and require the offending step be converted to `repeat: N`
68
+
62
69
  ## Output Format
63
70
 
64
71
  Respond with ONLY a JSON object in this exact shape:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "executant",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Harness for YAML-defined workflows that enables stepping through Claude sessions and bash commands",
5
5
  "repository": {
6
6
  "type": "git",