saasfunnels 0.4.0 → 0.4.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.
package/README.md CHANGED
@@ -51,7 +51,7 @@ approved set is reviewable.
51
51
  - uses: LeadEngine-ai/saasfunnels-cli/action@v0.2.1
52
52
  with:
53
53
  api-key: ${{ secrets.SAASFUNNELS_API_KEY }}
54
- cli-version: 0.2.1 # pin it; a floating version rebaselines drift
54
+ cli-version: 0.2.1 # pin it; a floating version rebaselines drift
55
55
  discovery-roots: app,lib
56
56
  ```
57
57
 
@@ -63,8 +63,8 @@ measured against a comparable scan rather than a wider or narrower one.
63
63
  Plan and pricing upload is opt-in:
64
64
 
65
65
  ```yaml
66
- plan-sources: "true"
67
- integration-id: <stripe integration id>
66
+ plan-sources: "true"
67
+ integration-id: <stripe integration id>
68
68
  ```
69
69
 
70
70
  It runs only on the default branch, requires a committed
@@ -100,7 +100,7 @@ Support: support@saasfunnels.ai
100
100
 
101
101
  Use `saasfunnels setup run` from the application repository after creating app credentials in Setup. Load `SAASFUNNELS_API_KEY` from **Developer setup access** into the local process environment. Its scopes are `developer:read` and `features:write`; it is not a browser or payment-administration key.
102
102
 
103
- The command asks before uploading structured discovery proposals. `--send` provides explicit non-interactive approval. It coordinates features, pricing extraction, and plan branches, reports progress, and waits for the Stripe catalog. Run `saasfunnels setup run --resume` after an interruption. Existing accepted stages and customer review decisions are preserved.
103
+ The command asks before uploading structured discovery proposals. `--send` provides explicit non-interactive approval. It coordinates features, pricing extraction, and plan branches, reports progress, and waits for the Stripe catalog. Run `saasfunnels setup run --resume` after an interruption. Existing accepted stages and customer review decisions are preserved. Coverage questions include a SHA-256 fingerprint of their affected files so changed application behavior can reopen an answer while unrelated file changes keep it. Only the fingerprint and bounded paths are uploaded, never source contents.
104
104
 
105
105
  The scanner reads supported application files one at a time without the old 500-file, 2 MB repository, or 200-candidate cutoffs. Generated code, tests, and symlinks are excluded. Files over 2 MB and uploads over the server limit stop with a specific recovery message.
106
106
 
package/dist/library.js CHANGED
@@ -235858,7 +235858,7 @@ function validateFeatureCatalogManifest(value) {
235858
235858
 
235859
235859
  // src/identity.ts
235860
235860
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
235861
- var SAASFUNNELS_CLI_VERSION = "0.4.0";
235861
+ var SAASFUNNELS_CLI_VERSION = "0.4.1";
235862
235862
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
235863
235863
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
235864
235864
  var SAASFUNNELS_ENV = Object.freeze({
@@ -233928,7 +233928,7 @@ function extractSetupPricing(source, kind, catalogPrices = [], options = {}) {
233928
233928
 
233929
233929
  // src/identity.ts
233930
233930
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
233931
- var SAASFUNNELS_CLI_VERSION = "0.4.0";
233931
+ var SAASFUNNELS_CLI_VERSION = "0.4.1";
233932
233932
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
233933
233933
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
233934
233934
  var SAASFUNNELS_ENV = Object.freeze({
@@ -233959,6 +233959,9 @@ function saasFunnelsPublicText(value) {
233959
233959
  ).replaceAll("prevenueFeatureKeys", "saasFunnelsFeatureKeys").replaceAll("PrevenueFeatureKey", "SaaSFunnelsFeatureKey").replaceAll("SaaSFunnels", SAASFUNNELS_PRODUCT_NAME);
233960
233960
  }
233961
233961
 
233962
+ // src/discovery-coverage.ts
233963
+ import { createHash as createHash3 } from "node:crypto";
233964
+
233962
233965
  // src/plan-analysis.ts
233963
233966
  var import_typescript3 = __toESM(require_typescript(), 1);
233964
233967
  function effect(node) {
@@ -237287,9 +237290,10 @@ async function discoverCoverageLimitations(cwd2) {
237287
237290
  );
237288
237291
  const sources = /* @__PURE__ */ new Map();
237289
237292
  const results = /* @__PURE__ */ new Map();
237293
+ const evidence = /* @__PURE__ */ new Map();
237290
237294
  const record3 = (code, file2) => {
237291
237295
  const paths = results.get(code) ?? /* @__PURE__ */ new Set();
237292
- if (paths.size < 12) paths.add(file2);
237296
+ paths.add(file2);
237293
237297
  results.set(code, paths);
237294
237298
  };
237295
237299
  for (const path of files) {
@@ -237302,6 +237306,14 @@ async function discoverCoverageLimitations(cwd2) {
237302
237306
  if (/\.(?:py|rb|go|rs|java|kt|cs|php|ex|exs|scala|swift|vue|svelte)$/.test(
237303
237307
  path
237304
237308
  )) {
237309
+ if (stat.size > 2e6)
237310
+ throw new Error(
237311
+ "Unsupported source exceeds the 2 MB scan limit. Narrow the discovery scope before retrying."
237312
+ );
237313
+ evidence.set(
237314
+ path,
237315
+ createHash3("sha256").update(await readFile3(join2(cwd2, path))).digest("hex")
237316
+ );
237305
237317
  record3("unsupported_language", path);
237306
237318
  continue;
237307
237319
  }
@@ -237309,6 +237321,7 @@ async function discoverCoverageLimitations(cwd2) {
237309
237321
  continue;
237310
237322
  const source = await readFile3(join2(cwd2, path), "utf8");
237311
237323
  sources.set(path, source);
237324
+ evidence.set(path, createHash3("sha256").update(source).digest("hex"));
237312
237325
  const file2 = parseApplicationSource(path, source);
237313
237326
  const visit = (node) => {
237314
237327
  if (!isProductNode(node)) return;
@@ -237351,11 +237364,20 @@ async function discoverCoverageLimitations(cwd2) {
237351
237364
  ))
237352
237365
  record3("dynamic_access", path);
237353
237366
  }
237354
- return [...results].map(([code, paths]) => ({ code, files: [...paths] }));
237367
+ return [...results].map(([code, paths]) => ({
237368
+ code,
237369
+ files: [...paths].sort().slice(0, 12),
237370
+ // Include all affected files in the receipt, even when the UI path list is capped.
237371
+ sourceFingerprint: createHash3("sha256").update(
237372
+ JSON.stringify(
237373
+ [...paths].sort().map((path) => [path, evidence.get(path)])
237374
+ )
237375
+ ).digest("hex")
237376
+ }));
237355
237377
  }
237356
237378
 
237357
237379
  // src/setup-run.ts
237358
- import { createHash as createHash4 } from "node:crypto";
237380
+ import { createHash as createHash5 } from "node:crypto";
237359
237381
  import { execFile as execFile4 } from "node:child_process";
237360
237382
  import { promisify as promisify4 } from "node:util";
237361
237383
  import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir3 } from "node:fs/promises";
@@ -237540,7 +237562,7 @@ function planMappingRequestKey(input) {
237540
237562
  }
237541
237563
 
237542
237564
  // src/plan-branches.ts
237543
- import { createHash as createHash3 } from "node:crypto";
237565
+ import { createHash as createHash4 } from "node:crypto";
237544
237566
  import { readdir as readdir3, readFile as readFile5, lstat as lstat4 } from "node:fs/promises";
237545
237567
  import { extname as extname3, join as join4, relative as relative3, resolve as resolve4, sep as sep2 } from "node:path";
237546
237568
  var maxPlanBranchFileCharacters = 2e6;
@@ -237562,7 +237584,7 @@ function clusterPlanBranches(branches) {
237562
237584
  const clusters = /* @__PURE__ */ new Map();
237563
237585
  for (const branch of branches) {
237564
237586
  const fullLocation = `${branch.repositoryPath}::${branch.symbol}`;
237565
- const location = fullLocation.length <= 400 ? fullLocation : `${fullLocation.slice(0, 367)}~${createHash3("sha256").update(fullLocation).digest("hex").slice(0, 32)}`;
237587
+ const location = fullLocation.length <= 400 ? fullLocation : `${fullLocation.slice(0, 367)}~${createHash4("sha256").update(fullLocation).digest("hex").slice(0, 32)}`;
237566
237588
  const key3 = `${branch.shape}|${location}`;
237567
237589
  const existing = clusters.get(key3);
237568
237590
  if (existing) existing.push(branch);
@@ -237652,7 +237674,7 @@ async function discoverPlanBranches(input) {
237652
237674
 
237653
237675
  // src/setup-run.ts
237654
237676
  var exec4 = promisify4(execFile4);
237655
- var hash2 = (text) => createHash4("sha256").update(text).digest("hex");
237677
+ var hash2 = (text) => createHash5("sha256").update(text).digest("hex");
237656
237678
  var contextSchema = external_exports.object({
237657
237679
  contractVersion: external_exports.literal(3),
237658
237680
  minimumCliVersion: external_exports.string(),
@@ -1,5 +1,5 @@
1
1
  export declare const SAASFUNNELS_CLI_NAME = "saasfunnels";
2
- export declare const SAASFUNNELS_CLI_VERSION = "0.4.0";
2
+ export declare const SAASFUNNELS_CLI_VERSION = "0.4.1";
3
3
  export declare const SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
4
4
  export declare const SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
5
5
  export declare const SAASFUNNELS_ENV: Readonly<{
@@ -77,7 +77,7 @@ export type SaaSFunnelsMcpOptions = {
77
77
  output?: Pick<NodeJS.WritableStream, "write">;
78
78
  };
79
79
  declare const mcpProtocolVersion = "2025-06-18";
80
- declare const mcpServerVersion = "0.4.0";
80
+ declare const mcpServerVersion = "0.4.1";
81
81
  export declare function saasFunnelsMcpResources(): McpResourceDefinition[];
82
82
  export declare function readSaaSFunnelsMcpResource(uri: string, options?: SaaSFunnelsMcpOptions): string;
83
83
  export declare function saasFunnelsMcpToolDefinitions({ allowFunnelTests, allowFunnelWrites, allowTestWrites, includeHostedAccountTools, }?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasfunnels",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "SaaSFunnels CLI and local stdio MCP server",
6
6
  "author": "SaaSFunnels",