@sweny-ai/core 0.1.169 → 0.1.171

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 (58) hide show
  1. package/dist/agent-env.d.ts +2 -1
  2. package/dist/agent-env.js +9 -3
  3. package/dist/browser.d.ts +2 -0
  4. package/dist/browser.js +3 -0
  5. package/dist/budget.js +5 -1
  6. package/dist/cli/comment-output.d.ts +4 -2
  7. package/dist/cli/comment-output.js +16 -8
  8. package/dist/cli/main.js +42 -26
  9. package/dist/cli/new.js +3 -0
  10. package/dist/cli/output.d.ts +3 -2
  11. package/dist/cli/output.js +39 -37
  12. package/dist/cli/progress.d.ts +33 -0
  13. package/dist/cli/progress.js +69 -0
  14. package/dist/cli/renderer.d.ts +4 -0
  15. package/dist/cli/renderer.js +20 -13
  16. package/dist/cli/run-output.d.ts +13 -6
  17. package/dist/cli/run-output.js +36 -26
  18. package/dist/cli/runs.js +17 -11
  19. package/dist/cli/style.d.ts +26 -0
  20. package/dist/cli/style.js +48 -0
  21. package/dist/cli/terminal.d.ts +47 -0
  22. package/dist/cli/terminal.js +123 -0
  23. package/dist/cli/ticket.d.ts +89 -0
  24. package/dist/cli/ticket.js +218 -0
  25. package/dist/cli/try.d.ts +15 -8
  26. package/dist/cli/try.js +84 -65
  27. package/dist/executor.d.ts +8 -1
  28. package/dist/executor.js +21 -2
  29. package/dist/git-credentials.d.ts +106 -0
  30. package/dist/git-credentials.js +322 -0
  31. package/dist/harness/acp.js +3 -0
  32. package/dist/harness/claude-code.d.ts +7 -0
  33. package/dist/harness/claude-code.js +44 -3
  34. package/dist/harness/codex.js +3 -0
  35. package/dist/harness/pi.js +3 -0
  36. package/dist/harness/policy.d.ts +5 -0
  37. package/dist/harness/policy.js +14 -0
  38. package/dist/harness/sandbox-wrapper.d.ts +9 -2
  39. package/dist/harness/sandbox-wrapper.js +14 -4
  40. package/dist/harness/types.d.ts +10 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/mermaid.d.ts +10 -5
  44. package/dist/mermaid.js +11 -13
  45. package/dist/safe-outputs.js +5 -0
  46. package/dist/skills/git-push.d.ts +62 -0
  47. package/dist/skills/git-push.js +132 -0
  48. package/dist/skills/github.d.ts +17 -0
  49. package/dist/skills/github.js +23 -0
  50. package/dist/testing.d.ts +16 -12
  51. package/dist/testing.js +45 -20
  52. package/dist/theme.d.ts +185 -0
  53. package/dist/theme.js +183 -0
  54. package/dist/types.d.ts +2 -0
  55. package/dist/workflows/browser.js +2 -2
  56. package/dist/workflows/implement.yml +2 -1
  57. package/dist/workflows/triage.yml +2 -1
  58. package/package.json +6 -1
package/dist/cli/try.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * `sweny try`: a zero-credential demo (#475).
3
3
  *
4
4
  * Replays a recorded explain-repo run through the REAL executor, with a replay
5
- * harness standing in for the agent, so the real progress line, answer block
6
- * (#460), receipt, policy segment and PR-comment markdown (#396) are what a
5
+ * harness standing in for the agent, so the real progress lines, answer block
6
+ * (#460), receipt ticket, policy stamp and PR comment (#396, #479) are what a
7
7
  * user sees. No agent, no model, no network, no env credentials, no files
8
8
  * written (no `.sweny/runs`, no journal). The environment handed to the
9
9
  * executor is empty by construction.
@@ -14,15 +14,19 @@
14
14
  import fs from "node:fs";
15
15
  import path from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
- import chalk from "chalk";
18
17
  import { parse as parseYaml } from "yaml";
19
18
  import { execute } from "../executor.js";
20
19
  import { validateParsed } from "../loader.js";
21
20
  import { WORKFLOW_TEMPLATES } from "./templates.js";
22
21
  import { formatFinalOutput, resolveFinalOutput } from "./final-output.js";
23
22
  import { formatRunComment } from "./comment-output.js";
24
- import { formatReceiptDuration, renderReceiptLine, summarizeRun } from "./run-output.js";
25
- import { c } from "./output.js";
23
+ import { formatReceiptDuration, summarizeRun } from "./run-output.js";
24
+ import { DagRenderer } from "./renderer.js";
25
+ import { createNodeProgress, toolCallsText } from "./progress.js";
26
+ import { createPaint } from "./style.js";
27
+ import { colorEnabled, glyphsFor, supportsUnicode, terminalColumns } from "./terminal.js";
28
+ import { writeReceipt } from "./ticket.js";
29
+ import { SWENY_TAGLINE } from "../theme.js";
26
30
  const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
27
31
  /** Shape check with a message naming the first bad field. Throws on a malformed fixture. */
28
32
  export function parseTryFixture(raw) {
@@ -152,34 +156,24 @@ export function createReplayHarness(fixture, opts) {
152
156
  }
153
157
  // ── Output ──────────────────────────────────────────────────────
154
158
  export const TRY_BANNER_TITLE = "Recorded demo";
155
- /** The real commands, same as the README quickstart. */
156
- export const TRY_NEXT_COMMANDS = [
157
- "npm install -g @sweny-ai/core",
158
- "sweny new --template explain-repo --yes",
159
- "sweny workflow run .sweny/workflows/explain-repo.yml",
160
- ];
159
+ /** The one command to run next: scaffolds the same workflow on the user's repo and says how to run it. */
160
+ export const TRY_NEXT_COMMAND = "npx @sweny-ai/core new --template explain-repo";
161
161
  /** Appended to `sweny try --help`. */
162
162
  export const TRY_HELP_NOTE = "The recording is ILLUSTRATIVE SAMPLE DATA (a fictional repo, hand-written timings and token counts), not output from a real run. " +
163
163
  "Nothing runs: no agent, no network, no credentials read, no files written.";
164
- export function formatTryBanner(fixture) {
165
- const lines = [
166
- `${c.brand("▲")} ${chalk.bold("sweny try")} ${chalk.bold.yellow(TRY_BANNER_TITLE)}, not a live run`,
167
- chalk.dim(` A replay of an ${fixture.workflow} run on a sample repo (${fixture.sample_repo}).`),
168
- chalk.dim(fixture.illustrative
169
- ? " The sample data is illustrative, not output from your code. No agent, no network, no credentials."
170
- : " No agent, no network, no credentials."),
171
- ];
172
- return lines.map((l, i) => (i === 0 ? ` ${l}` : l)).join("\n");
173
- }
174
- export function formatTryNext() {
164
+ /** The tagline, then the demo notice as one dim line. */
165
+ export function formatTryBanner(fixture, paint = createPaint(false), unicode = true) {
166
+ const g = glyphsFor(unicode);
167
+ const data = fixture.illustrative ? ", illustrative data" : "";
175
168
  return [
176
- ` ${chalk.bold("Run it for real")} on your own repo:`,
177
- "",
178
- ...TRY_NEXT_COMMANDS.map((cmd) => ` ${cmd}`),
179
- "",
180
- chalk.dim(" Needs a Claude login (`claude` signed in) or ANTHROPIC_API_KEY. Your run ends with the same receipt."),
169
+ ` ${paint.brand(g.brand)} ${paint.strong("SWEny")} ${SWENY_TAGLINE}`,
170
+ paint.muted(` ${TRY_BANNER_TITLE} of ${fixture.workflow} on a sample repo (${fixture.sample_repo})${data}. No agent, network or credentials.`),
181
171
  ].join("\n");
182
172
  }
173
+ /** The closing line: one command. */
174
+ export function formatTryNext(paint = createPaint(false)) {
175
+ return ` ${paint.muted("Run it on your repo:")} ${paint.strong(TRY_NEXT_COMMAND)}`;
176
+ }
183
177
  export const DEFAULT_TRY_PACE_MS = 1200;
184
178
  const silentLogger = { info() { }, warn() { }, error() { }, debug() { } };
185
179
  /** Replay the recording. Returns the process exit code. */
@@ -193,72 +187,97 @@ export async function runTry(opts = {}) {
193
187
  if (!workflow.nodes[id])
194
188
  throw new Error(`try-fixture.json: node "${id}" is not in ${fixture.workflow}`);
195
189
  }
196
- write(`\n${formatTryBanner(fixture)}\n\n`);
197
- const ticks = new Map();
190
+ const env = process.env;
191
+ const unicode = opts.unicode ?? supportsUnicode(env);
192
+ const color = tty && colorEnabled({ isTTY: true }, env);
193
+ const paint = createPaint(color);
194
+ const g = glyphsFor(unicode);
195
+ const columns = opts.columns ?? terminalColumns(process.stdout, env);
196
+ write(`\n${formatTryBanner(fixture, paint, unicode)}\n\n`);
197
+ const progress = createNodeProgress({
198
+ write,
199
+ live: tty,
200
+ unicode,
201
+ paint,
202
+ idWidth: Math.max(...Object.keys(fixture.nodes).map((id) => id.length)),
203
+ });
204
+ // The same events drive the DAG in the comment preview.
205
+ const dag = new DagRenderer(workflow, { legend: false, unicode });
198
206
  const observer = (event) => {
207
+ dag.update(event);
199
208
  switch (event.type) {
200
- case "workflow:start":
201
- write(` ${c.brand("▲")} ${chalk.bold(event.workflow)}\n\n`);
202
- break;
203
209
  case "node:enter":
204
- ticks.set(event.node, 0);
205
- if (tty)
206
- write(` ${c.subtle("○")} ${chalk.dim(event.node)}…`);
210
+ progress.enter(event.node);
207
211
  break;
208
- case "node:progress": {
209
- const n = (ticks.get(event.node) ?? 0) + 1;
210
- ticks.set(event.node, n);
211
- if (tty) {
212
- write(`\r\x1B[2K ${c.subtle("○")} ${chalk.dim(event.node)}… ${chalk.dim(`${n} tool ${n === 1 ? "call" : "calls"}`)}`);
213
- }
212
+ case "node:progress":
213
+ progress.tick(event.node);
214
214
  break;
215
- }
216
215
  case "node:exit": {
217
216
  const rec = fixture.nodes[event.node];
218
- const ok = event.result.status === "success";
219
- const icon = ok ? c.ok("✓") : c.fail("✗");
220
217
  const calls = event.result.toolCalls.length;
221
- const detail = [formatReceiptDuration(rec?.duration_ms ?? 0), ...(calls > 0 ? [`${calls} tool calls`] : [])];
222
- write(`${tty ? "\r\x1B[2K" : ""} ${icon} ${event.node} ${chalk.dim(detail.join(" · "))}\n`);
218
+ // The recording's timing, not the replay's wall clock.
219
+ const detail = [formatReceiptDuration(rec?.duration_ms ?? 0), ...(calls > 0 ? [toolCallsText(calls)] : [])];
220
+ const status = event.result.status === "success" ? "success" : event.result.status === "failed" ? "failed" : "skipped";
221
+ progress.exit(event.node, status, detail.join(` ${g.sep} `));
223
222
  break;
224
223
  }
225
224
  }
226
225
  };
227
- const { results, trace } = await execute(workflow, {}, {
228
- skills: new Map(),
229
- harness: createReplayHarness(fixture, { paceMs }),
230
- observer,
231
- logger: silentLogger,
232
- // Nothing for the executor to read: no credentials, no PATH, no config.
233
- env: {},
234
- offline: true,
235
- harnessPolicy: "warn",
236
- });
226
+ let executed;
227
+ try {
228
+ executed = await execute(workflow, {}, {
229
+ skills: new Map(),
230
+ harness: createReplayHarness(fixture, { paceMs }),
231
+ observer,
232
+ logger: silentLogger,
233
+ // Nothing for the executor to read: no credentials, no PATH, no config.
234
+ env: {},
235
+ offline: true,
236
+ harnessPolicy: "warn",
237
+ });
238
+ }
239
+ finally {
240
+ progress.stop();
241
+ }
242
+ const { results, trace } = executed;
237
243
  write("\n");
238
- // The recording's timings, not the replay's wall clock.
239
244
  const durationsMs = Object.fromEntries(Object.entries(fixture.nodes).map(([id, n]) => [id, n.duration_ms]));
240
245
  const totalMs = Object.values(durationsMs).reduce((a, b) => a + b, 0);
241
246
  const receipt = summarizeRun(results, totalMs, false, trace);
242
247
  const answer = resolveFinalOutput(workflow, results);
243
248
  if (answer)
244
249
  write(`${formatFinalOutput(answer)}\n\n`);
245
- write(` ${renderReceiptLine(receipt, tty)}\n\n`);
250
+ // Compact preview of the PR comment: its heading and DAG here, its ticket (the receipt) below.
246
251
  const comment = formatRunComment(workflow, results, receipt, { trace, durationsMs });
247
- write(` ${chalk.dim("What CI posts on a pull request (sweny workflow run --comment-file):")}\n\n`);
248
- write(`${comment
252
+ write(` ${paint.muted("On a pull request, CI posts this heading, this DAG and the receipt below:")}\n\n`);
253
+ const tone = receipt.ok ? paint.success : paint.error;
254
+ write(` ${tone(receipt.ok ? g.success : g.failure)} ${paint.strong(workflow.name)}\n\n`);
255
+ write(`${dag
256
+ .renderToString()
249
257
  .split("\n")
250
- .map((l) => (l === "" ? l : ` ${l}`))
251
- .join("\n")}\n`);
258
+ .slice(2)
259
+ .map((l) => (l === "" ? l : ` ${l}`))
260
+ .join("\n")}\n\n`);
261
+ await writeReceipt(receipt, {
262
+ workflow: workflow.id,
263
+ runId: "demo",
264
+ stream: { write, isTTY: tty, columns },
265
+ env,
266
+ rich: tty,
267
+ color,
268
+ unicode,
269
+ animate: !opts.fast,
270
+ });
252
271
  if (opts.commentFile) {
253
272
  try {
254
273
  fs.mkdirSync(path.dirname(path.resolve(opts.commentFile)), { recursive: true });
255
274
  fs.writeFileSync(opts.commentFile, comment);
256
275
  }
257
276
  catch (err) {
258
- process.stderr.write(` ⚠ could not write comment file: ${err instanceof Error ? err.message : err}\n`);
277
+ process.stderr.write(` ${g.warning} could not write comment file: ${err instanceof Error ? err.message : err}\n`);
259
278
  }
260
279
  }
261
- write(`${formatTryNext()}\n\n`);
280
+ write(`${formatTryNext(paint)}\n\n`);
262
281
  return receipt.ok ? 0 : 1;
263
282
  }
264
283
  export function registerTryCommand(program) {
@@ -274,7 +293,7 @@ export function registerTryCommand(program) {
274
293
  if (options.pace !== undefined) {
275
294
  paceMs = Number(options.pace);
276
295
  if (!Number.isFinite(paceMs) || paceMs < 0) {
277
- console.error(chalk.red(`\n --pace must be a non-negative number of milliseconds, got "${options.pace}"\n`));
296
+ console.error(createPaint(colorEnabled(process.stderr)).error(`\n --pace must be a non-negative number of milliseconds, got "${options.pace}"\n`));
278
297
  process.exitCode = 1;
279
298
  return;
280
299
  }
@@ -12,7 +12,8 @@
12
12
  *
13
13
  * This replaces ~8k lines of engine + recipe step code.
14
14
  */
15
- import type { Workflow, Skill, Tool, Claude, Observer, Logger, ExecutionResult } from "./types.js";
15
+ import type { Workflow, Skill, Tool, Claude, Observer, NodeResult, Logger, ExecutionResult } from "./types.js";
16
+ import "./skills/git-push.js";
16
17
  import type { HarnessPolicyMode } from "./harness/policy.js";
17
18
  import type { AgentHarness } from "./harness/types.js";
18
19
  import type { DeciderMode } from "./decider.js";
@@ -138,6 +139,12 @@ export declare class RouteEvaluationError extends Error {
138
139
  readonly node: string;
139
140
  constructor(node: string, message: string);
140
141
  }
142
+ /**
143
+ * The bounded prompt context for one node: `input` plus its dependencies' entries.
144
+ * Exported for the property tests only; not part of the package API (index.ts does not re-export it).
145
+ * @internal
146
+ */
147
+ export declare function buildBoundedContext(workflow: Workflow, nodeId: string, results: Map<string, NodeResult>, input: unknown, instruction: string): Record<string, unknown>;
141
148
  /**
142
149
  * Dry-run tool gate (#380). Only an explicit `access: "read"` passes; a tool
143
150
  * with no `access` is treated as a write so new tools fail safe.
package/dist/executor.js CHANGED
@@ -22,6 +22,9 @@ import { resolveExecutionModel } from "./model.js";
22
22
  import { buildToolAliases } from "./skills/index.js";
23
23
  import { validateWorkflow } from "./schema.js";
24
24
  import { grantedAgentEnv, resolveAgentAccess } from "./agent-env.js";
25
+ import { gitCredentialWarning, scanGitCredentials } from "./git-credentials.js";
26
+ // #473: arms the sweny-side branch push in `github_create_pr` (Node only; skills/ stays browser-safe).
27
+ import "./skills/git-push.js";
25
28
  import { fenceUntrusted } from "./untrusted.js";
26
29
  import { asClaude } from "./harness/compat.js";
27
30
  import { budgetGate, isToolClass, policyGate, resolveHarnessPolicy } from "./harness/policy.js";
@@ -89,6 +92,17 @@ export async function execute(workflow, input, options) {
89
92
  // `budget` and the caller's (`--max-tokens`, `--max-cost`).
90
93
  const budgetGuard = new BudgetGuard(minLimits(toLimits(workflow.budget), toLimits(options.budget)));
91
94
  const harnessPolicy = options.harnessPolicy ?? resolveHarnessPolicy(runEnv);
95
+ // #473: a checkout that persisted a git credential (actions/checkout's
96
+ // default). Each harness masks it for read-only and staged nodes, or reports
97
+ // the node degraded; this says so once per run. Paths and keys, never values.
98
+ try {
99
+ const credentialWarning = gitCredentialWarning(scanGitCredentials(process.cwd(), { env: runEnv }));
100
+ if (credentialWarning)
101
+ logger.warn(credentialWarning);
102
+ }
103
+ catch {
104
+ // A scan failure never stops a run; each node scans again before it starts.
105
+ }
92
106
  // Decision model (#357): shadow only. Null (the default) means no HTTP at all.
93
107
  const shadow = createShadowDecider(workflow.decider, options.decider, runEnv, (m) => logger.warn(m));
94
108
  if (shadow)
@@ -419,6 +433,7 @@ export async function execute(workflow, input, options) {
419
433
  const attemptBudget = budgetOn ? nodeBudget.attempt(signal) : undefined;
420
434
  try {
421
435
  result = await claude.run({
436
+ nodeId: currentId,
422
437
  instruction: currentInstruction,
423
438
  context,
424
439
  tools: trackedTools,
@@ -910,8 +925,12 @@ function contextDependencies(workflow, nodeId, instruction = "") {
910
925
  }
911
926
  return deps;
912
927
  }
913
- /** The bounded prompt context for one node: `input` plus its dependencies' entries. */
914
- function buildBoundedContext(workflow, nodeId, results, input, instruction) {
928
+ /**
929
+ * The bounded prompt context for one node: `input` plus its dependencies' entries.
930
+ * Exported for the property tests only; not part of the package API (index.ts does not re-export it).
931
+ * @internal
932
+ */
933
+ export function buildBoundedContext(workflow, nodeId, results, input, instruction) {
915
934
  const deps = contextDependencies(workflow, nodeId, instruction);
916
935
  return {
917
936
  input,
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Persisted git credentials in the checkout (#473).
3
+ *
4
+ * `actions/checkout` keeps the job token on disk by default
5
+ * (`persist-credentials: true`): v4 and v5 write it into `.git/config` as an
6
+ * `http.<origin>/.extraheader`; v6 and later write it to a separate file under
7
+ * `$RUNNER_TEMP` that `.git/config` pulls in with `includeIf.gitdir:...path`.
8
+ * Either way, any agent that can read the repo can read a token that pushes to
9
+ * it. Env scoping cannot reach a file.
10
+ *
11
+ * {@link scanGitCredentials} finds the files that hold such a credential, so a
12
+ * harness can make them unreadable to the agent of a read-only or staged node
13
+ * (the process wrapper's `denyRead`, Claude Code's `Read(...)` deny rules and
14
+ * SDK sandbox `denyRead`), or report the node `degraded` (refused under strict)
15
+ * where it cannot. The fix that needs no masking at all is
16
+ * `persist-credentials: false`: sweny pushes the PR branch itself
17
+ * (`github_create_pr`), with the skill's own token, in the sweny process.
18
+ *
19
+ * What counts as a credential:
20
+ * - `http.extraheader` / `http.<url>.extraheader` (the checkout's header);
21
+ * - `credential.helper` with an inline secret (`!f() { echo password=...; }; f`);
22
+ * a `store` helper's file (default `~/.git-credentials`, or `--file <path>`);
23
+ * - `url.<base>.insteadOf` / `pushInsteadOf` whose base carries userinfo with
24
+ * a password or a token, and `remote.<name>.url` / `pushurl` that do.
25
+ *
26
+ * Files scanned: the repo config (`<commondir>/config`), the worktree config
27
+ * (`<gitdir>/config.worktree`), the global config (`GIT_CONFIG_GLOBAL`, else
28
+ * `~/.gitconfig` and `$XDG_CONFIG_HOME/git/config`), and every file they
29
+ * include (`include.path`, and `includeIf.*.path` whatever the condition).
30
+ * Values never leave this module: findings carry the file and a redacted key.
31
+ */
32
+ export { gitCredentialGap } from "./harness/policy.js";
33
+ export type GitCredentialKind = "extraheader" | "credential-helper" | "credential-store" | "url-userinfo";
34
+ export interface GitCredentialFinding {
35
+ /** The file holding the credential (absolute, symlinks resolved). */
36
+ file: string;
37
+ /** The config key, with any embedded credential redacted (`url.<redacted>.insteadof`). */
38
+ key: string;
39
+ kind: GitCredentialKind;
40
+ }
41
+ export interface GitCredentialScan {
42
+ findings: GitCredentialFinding[];
43
+ /** Every file to keep from the agent, deduplicated, in scan order. */
44
+ files: string[];
45
+ }
46
+ export interface ScanGitCredentialsOptions {
47
+ env?: Record<string, string | undefined>;
48
+ /** Test seam. Default: `fs.readFileSync(p, "utf8")`; a throw means "not there". */
49
+ readFile?: (p: string) => string;
50
+ /** Test seam. Default: `fs.existsSync`. */
51
+ exists?: (p: string) => boolean;
52
+ }
53
+ /** Does a URL carry a credential in its userinfo (`https://user:pass@`, `https://<token>@`)? */
54
+ export declare function urlHasCredential(url: string): boolean;
55
+ /** Does a `credential.helper` value hold the secret itself (an inline shell helper)? */
56
+ export declare function helperHasInlineSecret(value: string): boolean;
57
+ /** The file a `store` credential helper writes, or undefined when the helper is not `store`. */
58
+ export declare function storeHelperFiles(value: string, env: Record<string, string | undefined>): string[] | undefined;
59
+ interface ConfigEntry {
60
+ section: string;
61
+ /** Case preserved, as git does. */
62
+ subsection?: string;
63
+ key: string;
64
+ value: string;
65
+ }
66
+ /**
67
+ * Minimal git-config parser: `[section]`, `[section "sub"]`, the legacy
68
+ * `[section.sub]`, `key = value`, bare `key` (boolean), quoted values with
69
+ * escapes, `#` and `;` comments, and `\` line continuations. Section and key
70
+ * names are lowercased; subsections keep their case. Enough to find keys; not
71
+ * a validator.
72
+ */
73
+ export declare function parseGitConfig(text: string): ConfigEntry[];
74
+ /** The repo's git dir and common dir for `cwd`, walking up to the filesystem root. */
75
+ export declare function findGitDirs(cwd: string, readFile: (p: string) => string, exists: (p: string) => boolean): {
76
+ gitDir: string;
77
+ commonDir: string;
78
+ } | undefined;
79
+ /**
80
+ * Find persisted git credentials visible from `cwd`. Pure apart from reading
81
+ * config files; never returns a credential value.
82
+ */
83
+ export declare function scanGitCredentials(cwd: string, opts?: ScanGitCredentialsOptions): GitCredentialScan;
84
+ /**
85
+ * The files to keep from one node's agent: the scan's files for a read-only
86
+ * node or a staged write node, nothing otherwise. A default write node keeps
87
+ * today's behavior (it may push with the checkout's credential); the
88
+ * recommended `persist-credentials: false` leaves nothing to find.
89
+ */
90
+ export declare function gitCredentialMask(cwd: string, node: {
91
+ readOnly: boolean;
92
+ staged: boolean;
93
+ }, opts?: ScanGitCredentialsOptions): string[];
94
+ /** The run-start warning for a checkout that persists a credential. Paths and kinds only. */
95
+ export declare function gitCredentialWarning(scan: GitCredentialScan): string | undefined;
96
+ /**
97
+ * The `NodePolicy.gitCredentials` fragment for one node run (#473): the
98
+ * credential files to keep from a read-only or staged node's agent, or
99
+ * nothing. Adapters spread it into the policy they gate and wrap with.
100
+ */
101
+ export declare function gitCredentialPolicy(cwd: string, node: {
102
+ readOnly: boolean;
103
+ noPush?: boolean;
104
+ }, opts?: ScanGitCredentialsOptions): {
105
+ gitCredentials?: string[];
106
+ };