@sweny-ai/core 0.1.140 → 0.1.142

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/cli/main.js CHANGED
@@ -34,7 +34,7 @@ import { createVerboseToolObserver } from "./verbose-observer.js";
34
34
  import { createRunLogger, renderReceiptLine, summarizeRun, writeStepSummary, WORKFLOW_RUN_DESCRIPTION, WORKFLOW_RUN_OPTIONS, } from "./run-output.js";
35
35
  import { writeRunComment } from "./comment-output.js";
36
36
  import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, validateWarnings, parsePositiveInt, } from "./config.js";
37
- import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, formatResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
37
+ import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, writeResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
38
38
  import { checkProviderConnectivity, detectClaudeCodeLogin, discoverWorkflowSkillIds, validateCheckInputs, } from "./check.js";
39
39
  import { registerSetupCommand } from "./setup.js";
40
40
  import { registerPublishCommand } from "./publish.js";
@@ -392,7 +392,7 @@ triageCmd.action(async (options) => {
392
392
  const durationMs = Date.now() - runStart;
393
393
  // Output
394
394
  if (config.json) {
395
- console.log(formatResultJson(results));
395
+ await writeResultJson(results);
396
396
  }
397
397
  else {
398
398
  console.log(formatDagResultHuman(results, durationMs, config));
@@ -477,7 +477,7 @@ triageCmd.action(async (options) => {
477
477
  }
478
478
  });
479
479
  // ── sweny implement ───────────────────────────────────────────────────
480
- const implementCmd = registerImplementCommand(program);
480
+ const implementCmd = registerImplementCommand(program).option("--json", "Output result as JSON", false);
481
481
  implementCmd.action(async (issueId, options) => {
482
482
  const fileConfig = loadConfigFile();
483
483
  // Build a minimal CliConfig for the implement command by merging CLI opts with env/file
@@ -604,17 +604,22 @@ implementCmd.action(async (issueId, options) => {
604
604
  catch {
605
605
  // silent
606
606
  }
607
+ if (config.json) {
608
+ await writeResultJson(results);
609
+ }
607
610
  if (hasFailed) {
608
611
  console.error(chalk.red(`\n Implement workflow failed\n`));
609
612
  process.exit(1);
610
613
  }
611
- const prResult = results.get("create_pr");
612
- const prUrl = prResult?.data?.prUrl;
613
- if (prUrl) {
614
- console.log(chalk.green(`\n PR created: ${prUrl}\n`));
615
- }
616
- else {
617
- console.log(chalk.green(`\n Implement workflow completed\n`));
614
+ if (!config.json) {
615
+ const prResult = results.get("create_pr");
616
+ const prUrl = prResult?.data?.prUrl;
617
+ if (prUrl) {
618
+ console.log(chalk.green(`\n PR created: ${prUrl}\n`));
619
+ }
620
+ else {
621
+ console.log(chalk.green(`\n Implement workflow completed\n`));
622
+ }
618
623
  }
619
624
  // Legacy /api/report back-compat — see triage path.
620
625
  try {
@@ -956,7 +961,7 @@ export async function workflowRunAction(file, options) {
956
961
  });
957
962
  }
958
963
  if (isJson) {
959
- process.stdout.write(JSON.stringify(Object.fromEntries(results), null, 2) + "\n");
964
+ await writeResultJson(results);
960
965
  process.exit(wfHasFailed ? 1 : 0);
961
966
  return;
962
967
  }
@@ -24,6 +24,8 @@ export declare function extractCredentialHint(err: unknown): string | null;
24
24
  export declare function formatCrashError(error: unknown): string;
25
25
  export declare function formatCheckResults(results: CheckResult[]): string;
26
26
  export declare function formatResultJson(results: Map<string, NodeResult>): string;
27
+ /** Drain terminal JSON before process.exit(), including when stdout is a pipe. */
28
+ export declare function writeResultJson(results: Map<string, NodeResult>): Promise<void>;
27
29
  /**
28
30
  * Format triage results as GitHub-flavored markdown for `$GITHUB_STEP_SUMMARY`.
29
31
  *
@@ -405,6 +405,17 @@ export function formatCheckResults(results) {
405
405
  export function formatResultJson(results) {
406
406
  return JSON.stringify(Object.fromEntries(results), null, 2);
407
407
  }
408
+ /** Drain terminal JSON before process.exit(), including when stdout is a pipe. */
409
+ export function writeResultJson(results) {
410
+ return new Promise((resolve, reject) => {
411
+ process.stdout.write(formatResultJson(results) + "\n", (error) => {
412
+ if (error)
413
+ reject(error);
414
+ else
415
+ resolve();
416
+ });
417
+ });
418
+ }
408
419
  export function formatDagResultMarkdown(results, durationMs, config, options = {}) {
409
420
  const duration = formatDuration(durationMs);
410
421
  const lines = [];
package/dist/executor.js CHANGED
@@ -171,8 +171,8 @@ export async function execute(workflow, input, options) {
171
171
  // Build context: input + all prior node results.
172
172
  // Each prior node entry is the node's `data` augmented with `evals`
173
173
  // (a Record<name, EvalResult> for downstream lookup like
174
- // `priorNode.evals.tests_run_clean.pass`). When `data` already has an
175
- // `evals` key, the data field wins (back-compat with existing workflows).
174
+ // `priorNode.evals.tests_run_clean.pass`). This namespace is reserved
175
+ // for runtime verdicts; an agent-provided `data.evals` never overrides it.
176
176
  const context = {
177
177
  input,
178
178
  ...Object.fromEntries([...results.entries()].map(([k, v]) => [k, buildPriorNodeContext(v)])),
@@ -488,23 +488,15 @@ function warnOnJudgeBudget(workflow, logger) {
488
488
  *
489
489
  * Spec contract (https://spec.sweny.ai/nodes/#evalresult-type): downstream
490
490
  * nodes can read `priorNode.evals.<name>.pass` via the natural lookup path.
491
- * To honor this without breaking back-compat with workflows that read
492
- * `priorNode.<dataField>` directly, we spread `data` first and then attach
493
- * an `evals` namespace keyed by evaluator name. If the agent's structured
494
- * output happens to include a literal `evals` field, that field wins (and
495
- * the lookup degrades to "evals not available" for downstream readers).
491
+ * Preserve ordinary data fields, but reserve `evals` for runtime verdicts.
492
+ * Agent output must not forge a pass for downstream `requires` gates,
493
+ * including when no evaluator ran (the trusted namespace is then empty).
496
494
  */
497
495
  function buildPriorNodeContext(result) {
498
496
  const data = (result.data ?? {});
499
497
  const evals = result.evals ?? [];
500
- if (evals.length === 0)
501
- return data;
502
- const evalsByName = {};
503
- for (const e of evals) {
504
- evalsByName[e.name] = e;
505
- }
506
- // Spread evals first so a data-side `evals` key shadows it (back-compat).
507
- return { evals: evalsByName, ...data };
498
+ const evalsByName = Object.fromEntries(evals.map((e) => [e.name, e]));
499
+ return { ...data, evals: evalsByName };
508
500
  }
509
501
  /**
510
502
  * Build the prior-node entry shown to the LLM route evaluator.
@@ -564,14 +556,9 @@ function buildRouteEvalEntry(result, sourceNode) {
564
556
  dataView = fullData;
565
557
  }
566
558
  const evals = result.evals ?? [];
567
- if (evals.length === 0)
568
- return { view: dataView, missing };
569
- const evalsByName = {};
570
- for (const e of evals) {
571
- evalsByName[e.name] = e;
572
- }
573
- // Spread evals first so a data-side `evals` key shadows it (back-compat).
574
- return { view: { evals: evalsByName, ...dataView }, missing };
559
+ const evalsByName = Object.fromEntries(evals.map((e) => [e.name, e]));
560
+ // Schema projection never grants agent data authority over runtime verdicts.
561
+ return { view: { ...dataView, evals: evalsByName }, missing };
575
562
  }
576
563
  /**
577
564
  * Return the declared output property names for a node, or null when the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.140",
3
+ "version": "0.1.142",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"