@sweny-ai/core 0.1.208 → 0.1.209

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.
@@ -18,6 +18,12 @@ import { type TryFixture } from "./try.js";
18
18
  export declare const RECORD_COLUMNS = 80;
19
19
  /** Lines longer than this wrap in the window, like a terminal would. */
20
20
  export declare const RECORD_WRAP = 96;
21
+ /**
22
+ * Items of each list the recorded answer shows (the rest is counted in the heading). The README's
23
+ * first screen has a 900px budget; the real answer is longer than that, so the recording shows a
24
+ * summary of it. `sweny try` in a terminal shows all of it.
25
+ */
26
+ export declare const RECORD_ANSWER_ITEMS = 2;
21
27
  /** Width of one terminal column in the window, in px. Every segment is placed at column * CELL_W. */
22
28
  export declare const CELL_W = 7.85;
23
29
  /** Left edge of column 0. */
@@ -22,6 +22,12 @@ import { loadTryFixture, runTry, SUMMARY_PREVIEW_LEAD } from "./try.js";
22
22
  export const RECORD_COLUMNS = 80;
23
23
  /** Lines longer than this wrap in the window, like a terminal would. */
24
24
  export const RECORD_WRAP = 96;
25
+ /**
26
+ * Items of each list the recorded answer shows (the rest is counted in the heading). The README's
27
+ * first screen has a 900px budget; the real answer is longer than that, so the recording shows a
28
+ * summary of it. `sweny try` in a terminal shows all of it.
29
+ */
30
+ export const RECORD_ANSWER_ITEMS = 2;
25
31
  const FONT_SIZE = 13;
26
32
  /** Width of one terminal column in the window, in px. Every segment is placed at column * CELL_W. */
27
33
  export const CELL_W = 7.85;
@@ -317,6 +323,7 @@ export async function recordTrySvg(fixture = loadTryFixture()) {
317
323
  rich: true,
318
324
  unicode: true,
319
325
  columns: RECORD_COLUMNS,
326
+ compactAnswer: RECORD_ANSWER_ITEMS,
320
327
  fixture,
321
328
  write: (s) => {
322
329
  out += s;
@@ -1,33 +1,73 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "illustrative": true,
4
- "notice": "ILLUSTRATIVE SAMPLE DATA. This is not output from a real run and not about your code. The repo (acme-notes-api) is fictional; timings, tool-call counts, token usage and cost are plausible numbers for a small repo, written by hand. Replace this file with a recorded real run (metadata plus the sample answer only) and set illustrative to false.",
3
+ "illustrative": false,
4
+ "captured_at": "2026-11-10T20:09:47.380Z",
5
+ "notice": "A replay of a real run of explain-repo on swenyai/sweny, started 2026-11-10T20:09:47Z (2026-11-10). Durations, tool-call counts, token usage and cost are as recorded; the answer is redacted. Made by `sweny try --capture`; do not edit by hand.",
5
6
  "workflow": "explain-repo",
6
- "sample_repo": "acme-notes-api",
7
- "harness": { "id": "claude-code", "version": "sample" },
8
- "policy": { "envScope": true, "sandbox": "auto", "sandboxStarted": true },
7
+ "sample_repo": "swenyai/sweny",
8
+ "harness": {
9
+ "id": "claude-code",
10
+ "version": "0.3.322"
11
+ },
12
+ "policy": {
13
+ "envScope": true,
14
+ "sandbox": "strict",
15
+ "sandboxStarted": true
16
+ },
9
17
  "nodes": {
10
18
  "survey": {
11
- "duration_ms": 18400,
12
- "tool_calls": { "Glob": 3, "Read": 7, "Grep": 2 },
13
- "usage": { "inputTokens": 21300, "outputTokens": 1150, "costUsd": 0.09, "numTurns": 8 }
19
+ "duration_ms": 15711,
20
+ "tool_calls": {
21
+ "Bash": 2
22
+ },
23
+ "usage": {
24
+ "inputTokens": 6,
25
+ "outputTokens": 2006,
26
+ "costUsd": 0.1197956,
27
+ "numTurns": 3
28
+ }
14
29
  },
15
30
  "explain": {
16
- "duration_ms": 9100,
17
- "tool_calls": {},
18
- "usage": { "inputTokens": 6800, "outputTokens": 640, "costUsd": 0.04, "numTurns": 1 },
31
+ "duration_ms": 7211,
32
+ "tool_calls": {
33
+ "StructuredOutput": 1
34
+ },
35
+ "usage": {
36
+ "inputTokens": 2,
37
+ "outputTokens": 877,
38
+ "costUsd": 0.0871939,
39
+ "numTurns": 2
40
+ },
19
41
  "output": {
20
- "purpose": "A small REST API for storing and searching notes, built with Express and TypeScript on top of SQLite. Meant as the backend for a notes app.",
21
- "how_to_run": ["npm install", "npm run build", "npm test", "npm start (serves on http://localhost:3000)"],
42
+ "purpose": "SWEny runs DAGs of coding-agent steps (Claude Code, Codex, pi, ACP agents) defined in YAML workflows, locally or in CI. It scopes each step's env and tools, validates output schemas, and records a receipt per run. For teams automating coding-agent work.",
43
+ "how_to_run": [
44
+ "Use Node >=22, then run: npm install (installs all workspaces)",
45
+ "npm run build",
46
+ "npm run typecheck",
47
+ "npm test (Vitest)",
48
+ "npm run lint",
49
+ "Run the CLI: node packages/core/dist/cli/main.js workflow run <file.yml> (the `sweny` bin)",
50
+ "Docs site: npm run dev:web",
51
+ "In CI: use the GitHub Action swenyai/sweny@v5 (action.yml)"
52
+ ],
22
53
  "key_files": [
23
- "src/server.ts: the entry point, wires routes and the database",
24
- "src/routes/notes.ts: the CRUD and search endpoints",
25
- "src/db/schema.sql: the tables, applied on first start",
26
- "test/notes.test.ts: the API tests, a good map of expected behavior"
54
+ "README.md, ARCHITECTURE.md, CONTRIBUTING.md, CLAUDE.md: start here for the concepts and the contributor rules",
55
+ "packages/core/src/executor.ts: runs the DAG",
56
+ "packages/core/src/cli/: CLI entry point (main.js is built from here)",
57
+ "packages/core/src/schema.ts, types.ts, loader.ts: workflow definition and validation",
58
+ "packages/core/src/node-policy.ts, agent-env.ts, safe-outputs.ts, untrusted.ts: security scoping and output checks",
59
+ "packages/core/src/journal.ts: run receipts",
60
+ "packages/core/src/workflows/, .sweny/workflows/, examples/: built-in and sample workflows",
61
+ "packages/mcp/src/index.ts: MCP server",
62
+ "packages/studio: visual workflow editor",
63
+ "action.yml and .github/workflows/ci.yml, release.yml: Action and CI/release"
27
64
  ],
28
65
  "risks": [
29
- "No auth on any route: fine locally, not safe to expose as is.",
30
- "The README does not mention the PORT or DB_PATH environment variables that src/server.ts reads."
66
+ "Only Claude Code is fully supported. Codex, pi and ACP agents are experimental.",
67
+ "Every push to main triggers release.yml, which publishes to npm, so merges ship straight away. There are no changesets.",
68
+ "The build, test and run commands above come from a survey of the root scripts, not from running them. Check them against package.json.",
69
+ "The repo has many moving parts: 5 packages, 2 Astro sites, the plugin and the Action. Run builds from the repo root so workspaces resolve.",
70
+ "Security-sensitive code (env scoping, input fencing, output checks) needs careful review. Read SECURITY.md first."
31
71
  ]
32
72
  }
33
73
  }
package/dist/cli/try.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  import type { Command } from "commander";
17
17
  import type { AgentHarness } from "../harness/types.js";
18
18
  import type { Workflow } from "../types.js";
19
+ import { type FinalOutput } from "./final-output.js";
19
20
  import { type Paint } from "./style.js";
20
21
  export interface TryFixtureNode {
21
22
  /** Recorded wall-clock time of the node. */
@@ -109,6 +110,12 @@ export interface TryOptions {
109
110
  rich?: boolean;
110
111
  /** Terminal width. Default: stdout's. */
111
112
  columns?: number;
113
+ /**
114
+ * Show at most this many items of each list in the answer, with the count in its heading
115
+ * ("How to run it (2 of 8):"). The recording uses it to stay README-sized; a real terminal run
116
+ * leaves it unset and shows the whole answer.
117
+ */
118
+ compactAnswer?: number;
112
119
  fixture?: TryFixture;
113
120
  }
114
121
  /** A default replay takes about this long in total, however long the real run took. */
@@ -117,6 +124,11 @@ export declare const REPLAY_TARGET_MS = 8000;
117
124
  export declare const REPLAY_NODE_MIN_MS = 250;
118
125
  /** Per-node replay time: the recorded durations scaled to {@link REPLAY_TARGET_MS} in total (never slowed down). */
119
126
  export declare function scaledPace(fixture: TryFixture, targetMs?: number): (nodeId: string) => number;
127
+ /**
128
+ * The answer with each list cut to its first `maxItems` items; the heading then says
129
+ * "<title> (<maxItems> of <total>):". Scalars, short lists and errors pass through. Pure.
130
+ */
131
+ export declare function compactAnswer(out: FinalOutput, maxItems: number): FinalOutput;
120
132
  /** The line that opens the job summary preview (record.ts cuts the preview from here). */
121
133
  export declare const SUMMARY_PREVIEW_LEAD = "In CI, the job summary gets this heading, this DAG and the receipt below:";
122
134
  /** Replay the recording. Returns the process exit code. */
package/dist/cli/try.js CHANGED
@@ -211,6 +211,41 @@ export function scaledPace(fixture, targetMs = REPLAY_TARGET_MS) {
211
211
  const scale = total > targetMs ? targetMs / total : 1;
212
212
  return (id) => Math.max(REPLAY_NODE_MIN_MS, Math.round((fixture.nodes[id]?.duration_ms ?? 0) * scale));
213
213
  }
214
+ /**
215
+ * The answer with each list cut to its first `maxItems` items; the heading then says
216
+ * "<title> (<maxItems> of <total>):". Scalars, short lists and errors pass through. Pure.
217
+ */
218
+ export function compactAnswer(out, maxItems) {
219
+ if (out.kind !== "result" || !Number.isInteger(maxItems) || maxItems < 1)
220
+ return out;
221
+ const src = out.lines;
222
+ const lines = [];
223
+ let i = 0;
224
+ while (i < src.length) {
225
+ const head = src[i];
226
+ // A list is a heading with no value ("Key files:") followed by "- " items and their indented continuations.
227
+ if (!/^[^\s-].*:$/.test(head) || !src[i + 1]?.startsWith("- ")) {
228
+ lines.push(head);
229
+ i++;
230
+ continue;
231
+ }
232
+ const items = [];
233
+ let j = i + 1;
234
+ while (j < src.length && (src[j].startsWith("- ") || src[j].startsWith(" "))) {
235
+ if (src[j].startsWith("- "))
236
+ items.push([src[j]]);
237
+ else
238
+ items[items.length - 1].push(src[j]);
239
+ j++;
240
+ }
241
+ if (items.length <= maxItems)
242
+ lines.push(head, ...items.flat());
243
+ else
244
+ lines.push(`${head.slice(0, -1)} (${maxItems} of ${items.length}):`, ...items.slice(0, maxItems).flat());
245
+ i = j;
246
+ }
247
+ return { ...out, lines };
248
+ }
214
249
  const silentLogger = { info() { }, warn() { }, error() { }, debug() { } };
215
250
  /** The line that opens the job summary preview (record.ts cuts the preview from here). */
216
251
  export const SUMMARY_PREVIEW_LEAD = "In CI, the job summary gets this heading, this DAG and the receipt below:";
@@ -291,8 +326,10 @@ export async function runTry(opts = {}) {
291
326
  const totalMs = Object.values(durationsMs).reduce((a, b) => a + b, 0);
292
327
  const receipt = summarizeRun(results, totalMs, false, trace);
293
328
  const answer = resolveFinalOutput(workflow, results);
294
- if (answer)
295
- write(`${formatFinalOutput(answer)}\n\n`);
329
+ if (answer) {
330
+ const shown = opts.compactAnswer ? compactAnswer(answer, opts.compactAnswer) : answer;
331
+ write(`${formatFinalOutput(shown)}\n\n`);
332
+ }
296
333
  // Compact preview of CI's job summary: heading and DAG here, receipt below.
297
334
  const comment = formatRunComment(workflow, results, receipt, { trace, durationsMs });
298
335
  write(` ${paint.muted(SUMMARY_PREVIEW_LEAD)}\n\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.208",
3
+ "version": "0.1.209",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"