@ekanos/cli 0.1.6 → 0.1.8

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 (47) hide show
  1. package/README.md +2 -2
  2. package/dist/commands/init.d.ts +36 -0
  3. package/dist/commands/init.js +126 -10
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/publish.d.ts +5 -0
  6. package/dist/commands/publish.js +1 -1
  7. package/dist/commands/publish.js.map +1 -1
  8. package/dist/commands/status.d.ts +27 -1
  9. package/dist/commands/status.js +145 -12
  10. package/dist/commands/status.js.map +1 -1
  11. package/dist/commands/upgrade.js +108 -7
  12. package/dist/commands/upgrade.js.map +1 -1
  13. package/dist/copy-tree.d.ts +48 -0
  14. package/dist/copy-tree.js +55 -0
  15. package/dist/copy-tree.js.map +1 -0
  16. package/dist/index.d.ts +6 -0
  17. package/dist/index.js +12 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/parse-argv.d.ts +7 -1
  20. package/dist/parse-argv.js +10 -2
  21. package/dist/parse-argv.js.map +1 -1
  22. package/dist/project-checks.d.ts +9 -0
  23. package/dist/project-checks.js +29 -0
  24. package/dist/project-checks.js.map +1 -1
  25. package/dist/project.d.ts +23 -0
  26. package/dist/project.js +66 -1
  27. package/dist/project.js.map +1 -1
  28. package/dist/publish-api.d.ts +48 -4
  29. package/dist/publish-api.js +32 -13
  30. package/dist/publish-api.js.map +1 -1
  31. package/dist/safe-fs.d.ts +1 -1
  32. package/dist/safe-fs.js.map +1 -1
  33. package/dist/sources-api.d.ts +1 -1
  34. package/dist/sources-api.js.map +1 -1
  35. package/dist/validate-findings.js +2 -1
  36. package/dist/validate-findings.js.map +1 -1
  37. package/package.json +1 -1
  38. package/templates/AGENTS.md.tmpl +57 -0
  39. package/templates/claude-skill.md.tmpl +7 -0
  40. package/templates/docs/api-notes.md.tmpl +21 -0
  41. package/templates/mcp.json.tmpl +8 -0
  42. package/templates/skills/build-integration/SKILL.md +178 -0
  43. package/templates/skills/build-integration/references/api-docs-research.md +93 -0
  44. package/templates/skills/build-integration/references/api-wiring.md +94 -0
  45. package/templates/skills/build-integration/references/widget-design.md +132 -0
  46. package/templates/skills/polish-widgets/SKILL.md +139 -0
  47. package/templates/skills/polish-widgets/references/patterns.md +269 -0
@@ -1 +1 @@
1
- {"version":3,"file":"parse-argv.js","sourceRoot":"","sources":["../src/parse-argv.ts"],"names":[],"mappings":"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","sourcesContent":["import { usageError } from './errors';\n\n/**\n * A hand-rolled argv parser — deliberately NOT commander/yargs/citty. The\n * agent-native envelope needs total control over how an unknown flag is\n * reported (exit 2 through the JSON envelope with a hint, never a library's own\n * `console.error` + `process.exit`), and it must never abbreviate, never\n * auto-print help, and never guess. Every off-ramp routes through `usageError`.\n *\n * Supports: `--flag` (boolean), `--key=value`, `--key value`, `--no-<bool>`\n * negation, positionals, and a `--` passthrough where everything after the\n * first bare `--` is captured verbatim (that argv belongs to `test`'s child).\n */\n\nexport type FlagType = 'boolean' | 'string';\n\nexport interface FlagSpec {\n type: FlagType;\n /** Optional default applied when the flag is absent. */\n default?: string | boolean;\n}\n\nexport type FlagSpecs = Record<string, FlagSpec>;\n\nexport interface ParsedArgs {\n positionals: string[];\n flags: Record<string, string | boolean>;\n /** Raw argv after the first standalone `--`, unparsed. */\n passthrough: string[];\n /** True when a standalone `--` was present (even with nothing after it). */\n hasPassthrough: boolean;\n}\n\nexport function parseArgv(\n argv: readonly string[],\n specs: FlagSpecs,\n): ParsedArgs {\n const positionals: string[] = [];\n const flags: Record<string, string | boolean> = {};\n const passthrough: string[] = [];\n let hasPassthrough = false;\n\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n\n // Everything after the first standalone `--` is passthrough, verbatim.\n if (token === '--') {\n hasPassthrough = true;\n passthrough.push(...argv.slice(i + 1));\n break;\n }\n\n // A bare positional (does not start with `-`), or a lone `-`.\n if (!token.startsWith('--')) {\n if (token.startsWith('-') && token !== '-') {\n throw usageError(\n `Unknown flag \"${token}\". Short flags are not supported.`,\n `Use the long form (e.g. \"--json\"). Run \"ekanos --help\" for the ` +\n `full flag list.`,\n );\n }\n positionals.push(token);\n continue;\n }\n\n // A long flag. Split an inline `--key=value`.\n const body = token.slice(2);\n const eq = body.indexOf('=');\n const hasInlineValue = eq !== -1;\n let name = hasInlineValue ? body.slice(0, eq) : body;\n const inlineValue = hasInlineValue ? body.slice(eq + 1) : undefined;\n\n // `--no-<bool>` negation.\n let negated = false;\n if (!hasInlineValue && name.startsWith('no-')) {\n const candidate = name.slice(3);\n if (specs[candidate]?.type === 'boolean') {\n negated = true;\n name = candidate;\n }\n }\n\n const spec = specs[name];\n if (!spec) {\n throw usageError(\n `Unknown flag \"--${hasInlineValue ? name : body}\".`,\n `Remove it or check the spelling. Run \"ekanos --help\" (or ` +\n `\"ekanos <command> --help\") for the accepted flags.`,\n );\n }\n\n if (spec.type === 'boolean') {\n if (hasInlineValue) {\n throw usageError(\n `Flag \"--${name}\" is a boolean and does not take a value.`,\n `Pass \"--${name}\" to enable it or \"--no-${name}\" to disable it — ` +\n `not \"--${name}=${inlineValue ?? ''}\".`,\n );\n }\n flags[name] = !negated;\n continue;\n }\n\n // String flag: value is inline (`--key=value`) or the next token\n // (`--key value`).\n if (hasInlineValue) {\n flags[name] = inlineValue!;\n continue;\n }\n\n const next = argv[i + 1];\n if (next === undefined || next === '--') {\n throw usageError(\n `Flag \"--${name}\" expects a value.`,\n `Provide one as \"--${name} <value>\" or \"--${name}=<value>\".`,\n );\n }\n flags[name] = next;\n i++;\n }\n\n // Apply declared defaults for flags the user did not pass.\n for (const [name, spec] of Object.entries(specs)) {\n if (spec.default !== undefined && !(name in flags)) {\n flags[name] = spec.default;\n }\n }\n\n return { positionals, flags, passthrough, hasPassthrough };\n}\n"]}
1
+ {"version":3,"file":"parse-argv.js","sourceRoot":"","sources":["../src/parse-argv.ts"],"names":[],"mappings":"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","sourcesContent":["import { usageError } from './errors';\n\n/**\n * A hand-rolled argv parser — deliberately NOT commander/yargs/citty. The\n * agent-native envelope needs total control over how an unknown flag is\n * reported (exit 2 through the JSON envelope with a hint, never a library's own\n * `console.error` + `process.exit`), and it must never abbreviate, never\n * auto-print help, and never guess. Every off-ramp routes through `usageError`.\n *\n * Supports: `--flag` (boolean), `--key=value`, `--key value`, `--no-<bool>`\n * negation, positionals, and a `--` passthrough where everything after the\n * first bare `--` is captured verbatim (that argv belongs to `test`'s child).\n */\n\nexport type FlagType = 'boolean' | 'string';\n\nexport interface FlagSpec {\n type: FlagType;\n /** Optional default applied when the flag is absent. */\n default?: string | boolean;\n /**\n * String flags only: every occurrence appends, and the parsed value is a\n * `string[]` (absent entirely when the flag was never passed). A repeatable\n * flag has no `default` — \"absent\" and \"empty\" must stay distinguishable.\n */\n repeatable?: boolean;\n}\n\nexport type FlagSpecs = Record<string, FlagSpec>;\n\nexport interface ParsedArgs {\n positionals: string[];\n flags: Record<string, string | boolean | string[]>;\n /** Raw argv after the first standalone `--`, unparsed. */\n passthrough: string[];\n /** True when a standalone `--` was present (even with nothing after it). */\n hasPassthrough: boolean;\n}\n\nexport function parseArgv(\n argv: readonly string[],\n specs: FlagSpecs,\n): ParsedArgs {\n const positionals: string[] = [];\n const flags: Record<string, string | boolean | string[]> = {};\n const passthrough: string[] = [];\n let hasPassthrough = false;\n\n const assignString = (name: string, spec: FlagSpec, value: string) => {\n if (!spec.repeatable) {\n flags[name] = value;\n return;\n }\n const existing = flags[name];\n flags[name] = Array.isArray(existing) ? [...existing, value] : [value];\n };\n\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n\n // Everything after the first standalone `--` is passthrough, verbatim.\n if (token === '--') {\n hasPassthrough = true;\n passthrough.push(...argv.slice(i + 1));\n break;\n }\n\n // A bare positional (does not start with `-`), or a lone `-`.\n if (!token.startsWith('--')) {\n if (token.startsWith('-') && token !== '-') {\n throw usageError(\n `Unknown flag \"${token}\". Short flags are not supported.`,\n `Use the long form (e.g. \"--json\"). Run \"ekanos --help\" for the ` +\n `full flag list.`,\n );\n }\n positionals.push(token);\n continue;\n }\n\n // A long flag. Split an inline `--key=value`.\n const body = token.slice(2);\n const eq = body.indexOf('=');\n const hasInlineValue = eq !== -1;\n let name = hasInlineValue ? body.slice(0, eq) : body;\n const inlineValue = hasInlineValue ? body.slice(eq + 1) : undefined;\n\n // `--no-<bool>` negation.\n let negated = false;\n if (!hasInlineValue && name.startsWith('no-')) {\n const candidate = name.slice(3);\n if (specs[candidate]?.type === 'boolean') {\n negated = true;\n name = candidate;\n }\n }\n\n const spec = specs[name];\n if (!spec) {\n throw usageError(\n `Unknown flag \"--${hasInlineValue ? name : body}\".`,\n `Remove it or check the spelling. Run \"ekanos --help\" (or ` +\n `\"ekanos <command> --help\") for the accepted flags.`,\n );\n }\n\n if (spec.type === 'boolean') {\n if (hasInlineValue) {\n throw usageError(\n `Flag \"--${name}\" is a boolean and does not take a value.`,\n `Pass \"--${name}\" to enable it or \"--no-${name}\" to disable it — ` +\n `not \"--${name}=${inlineValue ?? ''}\".`,\n );\n }\n flags[name] = !negated;\n continue;\n }\n\n // String flag: value is inline (`--key=value`) or the next token\n // (`--key value`).\n if (hasInlineValue) {\n assignString(name, spec, inlineValue!);\n continue;\n }\n\n const next = argv[i + 1];\n if (next === undefined || next === '--') {\n throw usageError(\n `Flag \"--${name}\" expects a value.`,\n `Provide one as \"--${name} <value>\" or \"--${name}=<value>\".`,\n );\n }\n assignString(name, spec, next);\n i++;\n }\n\n // Apply declared defaults for flags the user did not pass.\n for (const [name, spec] of Object.entries(specs)) {\n if (spec.default !== undefined && !(name in flags)) {\n flags[name] = spec.default;\n }\n }\n\n return { positionals, flags, passthrough, hasPassthrough };\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { Finding } from '@ekanos/integration-schema';
2
+ import { type EkanosProject } from './project.js';
2
3
  /**
3
4
  * Project-level integrity checks that read files in the project root (next to
4
5
  * ekanos.json), NOT the integration definition. These are deliberately kept
@@ -16,3 +17,11 @@ import type { Finding } from '@ekanos/integration-schema';
16
17
  * runner on its own). If a config IS present, it MUST inline the SDK.
17
18
  */
18
19
  export declare function collectProjectFindings(projectDir: string): Finding[];
20
+ /**
21
+ * Advisory check on the `docs` array: a relative entry that resolves to
22
+ * nothing on disk gets a WARNING — never an error, because a missing doc file
23
+ * must not block a publish (the gate refuses only error-severity findings).
24
+ * URLs are never checked: `validate` does no network, ever, and an agent
25
+ * dereferences them at authoring time instead.
26
+ */
27
+ export declare function collectDocsFindings(project: EkanosProject, projectDir: string): Finding[];
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { isDocUrl } from './project.js';
3
4
  const VITEST_CONFIG_NAMES = [
4
5
  'vitest.config.ts',
5
6
  'vitest.config.mts',
@@ -60,6 +61,34 @@ export function collectProjectFindings(projectDir) {
60
61
  },
61
62
  ];
62
63
  }
64
+ /**
65
+ * Advisory check on the `docs` array: a relative entry that resolves to
66
+ * nothing on disk gets a WARNING — never an error, because a missing doc file
67
+ * must not block a publish (the gate refuses only error-severity findings).
68
+ * URLs are never checked: `validate` does no network, ever, and an agent
69
+ * dereferences them at authoring time instead.
70
+ */
71
+ export function collectDocsFindings(project, projectDir) {
72
+ var _a;
73
+ const findings = [];
74
+ for (const entry of (_a = project.docs) !== null && _a !== void 0 ? _a : []) {
75
+ if (isDocUrl(entry))
76
+ continue;
77
+ const resolved = path.resolve(projectDir, entry);
78
+ if (fs.existsSync(resolved))
79
+ continue;
80
+ findings.push({
81
+ check: 'docs/missing-file',
82
+ severity: 'warn',
83
+ file: resolved,
84
+ message: `ekanos.json lists "${entry}" under "docs", but no file exists ` +
85
+ `there (resolved to ${resolved}).`,
86
+ hint: `Create ${entry}, or fix the path in ekanos.json's "docs" array so ` +
87
+ 'it points at real API documentation.',
88
+ });
89
+ }
90
+ return findings;
91
+ }
63
92
  /**
64
93
  * True when the config source contains an `inline: [ … ]` array that lists
65
94
  * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module
@@ -1 +1 @@
1
- {"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,mBAAmB,GAAG;IAC1B,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,iBAAiB;CAClB,CAAC;AAEF,gFAAgF;AAChF,MAAM,qBAAqB,GAAG;IAC5B,SAAS;IACT,aAAa;IACb,aAAa;IACb,gCAAgC;IAChC,QAAQ;IACR,MAAM;IACN,GAAG;CACJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,MAAM,UAAU,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAClD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAC5B,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhD,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAE3B,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACnD,IAAI,gBAAgB,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAExC,OAAO;QACL;YACE,KAAK,EAAE,2BAA2B;YAClC,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,UAAU;YAChB,OAAO,EACL,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,oCAAoC;gBAChE,mEAAmE;gBACnE,sEAAsE;gBACtE,mEAAmE;gBACnE,kBAAkB;YACpB,IAAI,EACF,8DAA8D;gBAC9D,uCAAuC,qBAAqB,EAAE;SACjE;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,MAAc;;IACtC,MAAM,kBAAkB,GAAG,8BAA8B,CAAC;IAC1D,KACE,IAAI,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAC3C,KAAK,KAAK,IAAI,EACd,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EACvC,CAAC;QACD,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAA,KAAK,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;IAC/D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nconst VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n"]}
1
+ {"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { type EkanosProject, isDocUrl } from './project';\n\nconst VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * Advisory check on the `docs` array: a relative entry that resolves to\n * nothing on disk gets a WARNING — never an error, because a missing doc file\n * must not block a publish (the gate refuses only error-severity findings).\n * URLs are never checked: `validate` does no network, ever, and an agent\n * dereferences them at authoring time instead.\n */\nexport function collectDocsFindings(\n project: EkanosProject,\n projectDir: string,\n): Finding[] {\n const findings: Finding[] = [];\n\n for (const entry of project.docs ?? []) {\n if (isDocUrl(entry)) continue;\n\n const resolved = path.resolve(projectDir, entry);\n if (fs.existsSync(resolved)) continue;\n\n findings.push({\n check: 'docs/missing-file',\n severity: 'warn',\n file: resolved,\n message:\n `ekanos.json lists \"${entry}\" under \"docs\", but no file exists ` +\n `there (resolved to ${resolved}).`,\n hint:\n `Create ${entry}, or fix the path in ekanos.json's \"docs\" array so ` +\n 'it points at real API documentation.',\n });\n }\n\n return findings;\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n"]}
package/dist/project.d.ts CHANGED
@@ -1,4 +1,15 @@
1
1
  import { z } from 'zod';
2
+ /** True when a `docs` entry is a URL (never touched by local checks). */
3
+ export declare function isDocUrl(entry: string): boolean;
4
+ /**
5
+ * Why a `docs` entry is invalid, or null when it is fine. One rule, shared by
6
+ * the schema below and `init --docs`, so "valid on the command line" and
7
+ * "valid in ekanos.json" cannot drift apart: an entry is either an http(s)
8
+ * URL or a path relative to the project root. Absolute paths are rejected
9
+ * because they are meaningless on anyone else's machine, and non-http(s)
10
+ * schemes because nothing here will ever dereference them.
11
+ */
12
+ export declare function docEntryProblem(entry: string): string | null;
2
13
  /** One integration: the pair that addresses a definition module. */
3
14
  declare const IntegrationEntrySchema: z.ZodObject<{
4
15
  slug: z.ZodString;
@@ -37,6 +48,14 @@ export declare const EkanosProjectSchema: z.ZodEffects<z.ZodObject<{
37
48
  * fallback after `--host` and `EKANOS_HOST`.
38
49
  */
39
50
  host: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
51
+ /**
52
+ * Where the target API's documentation lives — http(s) URLs and
53
+ * project-relative paths, in the order an agent should try them. Written
54
+ * by `init --docs`, read by agents (see AGENTS.md's "API documentation")
55
+ * and advisory-checked by `validate`. Optional and allowed empty: a
56
+ * project that predates the field stays valid.
57
+ */
58
+ docs: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
40
59
  sourceGlobs: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, "many">>;
41
60
  }, "strict", z.ZodTypeAny, {
42
61
  slug?: string | undefined;
@@ -48,6 +67,7 @@ export declare const EkanosProjectSchema: z.ZodEffects<z.ZodObject<{
48
67
  }[] | undefined;
49
68
  source?: string | undefined;
50
69
  host?: string | undefined;
70
+ docs?: string[] | undefined;
51
71
  sourceGlobs?: string[] | undefined;
52
72
  }, {
53
73
  slug?: string | undefined;
@@ -59,6 +79,7 @@ export declare const EkanosProjectSchema: z.ZodEffects<z.ZodObject<{
59
79
  }[] | undefined;
60
80
  source?: string | undefined;
61
81
  host?: string | undefined;
82
+ docs?: string[] | undefined;
62
83
  sourceGlobs?: string[] | undefined;
63
84
  }>, {
64
85
  slug?: string | undefined;
@@ -70,6 +91,7 @@ export declare const EkanosProjectSchema: z.ZodEffects<z.ZodObject<{
70
91
  }[] | undefined;
71
92
  source?: string | undefined;
72
93
  host?: string | undefined;
94
+ docs?: string[] | undefined;
73
95
  sourceGlobs?: string[] | undefined;
74
96
  }, {
75
97
  slug?: string | undefined;
@@ -81,6 +103,7 @@ export declare const EkanosProjectSchema: z.ZodEffects<z.ZodObject<{
81
103
  }[] | undefined;
82
104
  source?: string | undefined;
83
105
  host?: string | undefined;
106
+ docs?: string[] | undefined;
84
107
  sourceGlobs?: string[] | undefined;
85
108
  }>;
86
109
  export type EkanosProject = z.infer<typeof EkanosProjectSchema>;
package/dist/project.js CHANGED
@@ -34,6 +34,63 @@ const SourceGlobSchema = z
34
34
  .refine((glob) => !glob.split('/').includes('..'), {
35
35
  message: 'a sourceGlobs entry must not escape the project root with "..".',
36
36
  });
37
+ /** Anything with a scheme prefix — `https:`, `file:`, a Windows drive `C:`. */
38
+ const URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;
39
+ /** True when a `docs` entry is a URL (never touched by local checks). */
40
+ export function isDocUrl(entry) {
41
+ return /^https?:\/\//i.test(entry);
42
+ }
43
+ /**
44
+ * Why a `docs` entry is invalid, or null when it is fine. One rule, shared by
45
+ * the schema below and `init --docs`, so "valid on the command line" and
46
+ * "valid in ekanos.json" cannot drift apart: an entry is either an http(s)
47
+ * URL or a path relative to the project root. Absolute paths are rejected
48
+ * because they are meaningless on anyone else's machine, and non-http(s)
49
+ * schemes because nothing here will ever dereference them.
50
+ */
51
+ export function docEntryProblem(entry) {
52
+ if (entry.trim() === '')
53
+ return 'a docs entry must not be empty.';
54
+ if (URL_SCHEME_PATTERN.test(entry)) {
55
+ if (!isDocUrl(entry)) {
56
+ return (`"${entry}" is neither an http(s) URL nor a relative path — a docs ` +
57
+ 'entry is a URL to API documentation or a path relative to the ' +
58
+ 'project root.');
59
+ }
60
+ try {
61
+ new URL(entry);
62
+ return null;
63
+ }
64
+ catch (_a) {
65
+ return `"${entry}" is not a valid http(s) URL.`;
66
+ }
67
+ }
68
+ if (path.isAbsolute(entry)) {
69
+ return (`"${entry}" is an absolute path — a docs entry that is a file must be ` +
70
+ 'relative to the project root, so it means the same thing on every ' +
71
+ 'machine that clones the project.');
72
+ }
73
+ // Same rule as SourceGlobSchema, for the same reason: a `..` segment lets a
74
+ // relative entry escape the project root, and `validate` resolves these
75
+ // paths for the missing-file check.
76
+ if (entry.split(/[\\/]/).includes('..')) {
77
+ return (`"${entry}" escapes the project root with ".." — a docs entry that is ` +
78
+ 'a file must stay inside the project.');
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * One `docs` entry: where the target API's documentation lives. An http(s)
84
+ * URL (an OpenAPI spec, the official docs site) or a project-relative path
85
+ * (a vendored spec, `docs/api-notes.md`). Local paths are advisory-checked by
86
+ * `validate` (`docs/missing-file`, a warning); URLs are never fetched.
87
+ */
88
+ const DocEntrySchema = z.string().superRefine((entry, ctx) => {
89
+ const problem = docEntryProblem(entry);
90
+ if (problem) {
91
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem });
92
+ }
93
+ });
37
94
  /** One integration: the pair that addresses a definition module. */
38
95
  const IntegrationEntrySchema = z
39
96
  .object({ slug: SlugSchema, entry: EntrySchema })
@@ -108,6 +165,14 @@ export const EkanosProjectSchema = z
108
165
  * fallback after `--host` and `EKANOS_HOST`.
109
166
  */
110
167
  host: HostSchema.optional(),
168
+ /**
169
+ * Where the target API's documentation lives — http(s) URLs and
170
+ * project-relative paths, in the order an agent should try them. Written
171
+ * by `init --docs`, read by agents (see AGENTS.md's "API documentation")
172
+ * and advisory-checked by `validate`. Optional and allowed empty: a
173
+ * project that predates the field stays valid.
174
+ */
175
+ docs: z.array(DocEntrySchema).optional(),
111
176
  // There is deliberately no `harness` field. One existed, accepted by the
112
177
  // schema and read by nothing — the same accept-and-ignore that made
113
178
  // `sourceGlobs` cost a partner a silently unstyled integration. `.strict()`
@@ -199,7 +264,7 @@ export function loadProject(dir) {
199
264
  const where = first && first.path.length > 0 ? first.path.join('.') : '(root)';
200
265
  throw preconditionError(`${EKANOS_CONFIG_FILENAME} is invalid at "${where}": ${(_a = first === null || first === void 0 ? void 0 : first.message) !== null && _a !== void 0 ? _a : 'schema violation'}`, `Correct ${configPath} so it matches the ekanos.json schema ` +
201
266
  `({ slug, entry } or { integrations: [...] }, plus optional ` +
202
- `source, host and sourceGlobs).`);
267
+ `source, host, docs and sourceGlobs).`);
203
268
  }
204
269
  const project = result.data;
205
270
  const integrations = projectIntegrations(project).map((declared) => {
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nimport { notFoundError, preconditionError } from './errors';\n\nconst SlugSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message:\n 'slug must be kebab-case ([a-z0-9] segments separated by single hyphens), e.g. \"acme-crm\".',\n});\n\nconst EntrySchema = z\n .string()\n .min(1, { message: 'entry must be a path to the integration module.' });\n\n/**\n * Extra Tailwind content globs, for an integration that does not live under\n * the default layout. They are interpolated into the generated\n * `styles/globals.css` as `@source` lines, so they are validated as\n * DATA-IN-CSS: project-relative, no escaping the project, and none of the\n * characters that could close the string or the surrounding comment.\n *\n * Being strict here is deliberate. A glob that matches nothing costs a partner\n * a silently unstyled integration; a glob that is rejected costs them one\n * clear error.\n */\nconst SOURCE_GLOB_PATTERN = /^[A-Za-z0-9_\\-./*{},[\\]!]+$/;\n\nconst SourceGlobSchema = z\n .string()\n .min(1, { message: 'a sourceGlobs entry must not be empty.' })\n .max(200, { message: 'a sourceGlobs entry must be under 200 characters.' })\n .regex(SOURCE_GLOB_PATTERN, {\n message:\n 'a sourceGlobs entry may contain only letters, digits and _-./*{},[]! ' +\n '— it is written verbatim into the generated stylesheet.',\n })\n .refine((glob) => !glob.startsWith('/'), {\n message: 'a sourceGlobs entry must be relative to the project root.',\n })\n .refine((glob) => !glob.split('/').includes('..'), {\n message: 'a sourceGlobs entry must not escape the project root with \"..\".',\n });\n\n/** One integration: the pair that addresses a definition module. */\nconst IntegrationEntrySchema = z\n .object({ slug: SlugSchema, entry: EntrySchema })\n .strict();\n\nexport type IntegrationEntry = z.infer<typeof IntegrationEntrySchema>;\n\n/**\n * `ekanos.json` — the project contract. Written by `init`, read by `validate`,\n * `dev` and `test`.\n *\n * Two shapes, because a project can hold more than one integration and the\n * single-integration form is what `init` writes and what most projects keep:\n *\n * ```json\n * { \"slug\": \"acme-crm\", \"entry\": \"src/integration.ts\" }\n *\n * { \"integrations\": [\n * { \"slug\": \"acme-crm\", \"entry\": \"src/crm.ts\" },\n * { \"slug\": \"acme-billing\", \"entry\": \"src/billing.ts\" }\n * ] }\n * ```\n *\n * A second integration used to be invisible to the CLI entirely — the harness\n * registry is an array and `collectCollisionFindings` exists precisely to\n * cross-check several definitions against each other, but `validate` could\n * only ever see one. Both forms normalise to the same list, so nothing\n * downstream has to know which was written.\n */\n/**\n * The Fusion source this project publishes to, by slug. Fusion source slugs\n * are opaque server-side identifiers, so the only local claims are \"one\n * token, sanely sized\" — the server resolves and authorizes it.\n */\nconst SourceSlugSchema = z\n .string()\n .min(1, { message: 'source must not be empty.' })\n .max(255, { message: 'source must be at most 255 characters.' })\n .regex(/^\\S+$/, { message: 'source must not contain whitespace.' });\n\n/**\n * The Fusion host this project publishes to. Origin-shaped: the same\n * normalization the credential store applies (`normalizeHost`) accepts it, so\n * \"valid here\" and \"usable as a session key\" cannot drift apart.\n */\nconst HostSchema = z\n .string()\n .min(1, { message: 'host must not be empty.' })\n .max(255, { message: 'host must be at most 255 characters.' })\n .refine(\n (value) => {\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:';\n } catch {\n return false;\n }\n },\n {\n message:\n 'host must be a full http(s) origin, e.g. \"https://app.example.com\".',\n },\n );\n\nexport const EkanosProjectSchema = z\n .object({\n $schema: z.string().optional(),\n slug: SlugSchema.optional(),\n entry: EntrySchema.optional(),\n integrations: z.array(IntegrationEntrySchema).optional(),\n /**\n * Written by the first successful `ekanos publish --source <slug>` so\n * later publishes need no flag. Read only by `publish`.\n */\n source: SourceSlugSchema.optional(),\n /**\n * The Fusion host this project publishes to — an http(s) origin, e.g.\n * \"https://app.example.com\". Written by the first successful `ekanos\n * publish` (alongside `source`), read by every host-taking verb as the\n * fallback after `--host` and `EKANOS_HOST`.\n */\n host: HostSchema.optional(),\n // There is deliberately no `harness` field. One existed, accepted by the\n // schema and read by nothing — the same accept-and-ignore that made\n // `sourceGlobs` cost a partner a silently unstyled integration. `.strict()`\n // now rejects it by name, which is the honest answer until something\n // actually consumes it.\n sourceGlobs: z.array(SourceGlobSchema).optional(),\n })\n .strict()\n .superRefine((value, ctx) => {\n const hasSingle = value.slug !== undefined || value.entry !== undefined;\n const hasList = value.integrations !== undefined;\n\n if (hasSingle && hasList) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'use either the single form ({ slug, entry }) or the list form ' +\n '({ integrations: [...] }), not both — two places to declare the ' +\n 'same thing is how they drift apart.',\n });\n return;\n }\n\n if (hasList) {\n if (value.integrations!.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations'],\n message: 'integrations must list at least one integration.',\n });\n }\n const seen = new Set<string>();\n value.integrations!.forEach((entry, index) => {\n if (seen.has(entry.slug)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations', index, 'slug'],\n message:\n `duplicate slug \"${entry.slug}\" — every integration in a ` +\n 'project needs its own, since the slug addresses it everywhere.',\n });\n }\n seen.add(entry.slug);\n });\n return;\n }\n\n if (value.slug === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slug'],\n message: 'slug is required (or use \"integrations\": [...]).',\n });\n }\n if (value.entry === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['entry'],\n message: 'entry is required (or use \"integrations\": [...]).',\n });\n }\n });\n\nexport type EkanosProject = z.infer<typeof EkanosProjectSchema>;\n\n/** Both `ekanos.json` shapes, flattened to the one the CLI works in. */\nexport function projectIntegrations(\n project: EkanosProject,\n): IntegrationEntry[] {\n if (project.integrations !== undefined) return [...project.integrations];\n return [{ slug: project.slug!, entry: project.entry! }];\n}\n\nexport const EKANOS_CONFIG_FILENAME = 'ekanos.json';\n\n/** One integration from `ekanos.json`, with its entry resolved on disk. */\nexport interface LoadedIntegration {\n slug: string;\n entry: string;\n /** Absolute path to the resolved entry module. */\n entryPath: string;\n}\n\nexport interface LoadedProject {\n project: EkanosProject;\n /** Absolute path to the directory containing ekanos.json (the project root). */\n projectDir: string;\n /** Absolute path to ekanos.json itself. */\n configPath: string;\n /** Every integration the project declares, in declaration order. */\n integrations: LoadedIntegration[];\n /**\n * The first integration. It names the generated harness shell and is what a\n * single-integration project means by \"the\" integration; nothing else\n * privileges it.\n */\n primary: LoadedIntegration;\n}\n\n/**\n * Read and validate ekanos.json from a directory. Missing file → not-found\n * (exit 6); malformed JSON or a schema violation → precondition failed\n * (exit 9), because a project the CLI cannot even read is a precondition for\n * every verb, not a definition-level validation finding.\n */\nexport function loadProject(dir: string): LoadedProject {\n const projectDir = path.resolve(dir);\n const configPath = path.join(projectDir, EKANOS_CONFIG_FILENAME);\n\n if (!fs.existsSync(configPath)) {\n throw notFoundError(\n `No ${EKANOS_CONFIG_FILENAME} found in ${projectDir}.`,\n `Run \"ekanos init\" here first, or change into the project directory ` +\n `that contains ${EKANOS_CONFIG_FILENAME}.`,\n );\n }\n\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n } catch (error) {\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Fix the JSON syntax in ${configPath}.`,\n );\n }\n\n const result = EkanosProjectSchema.safeParse(raw);\n if (!result.success) {\n const first = result.error.issues[0];\n const where =\n first && first.path.length > 0 ? first.path.join('.') : '(root)';\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is invalid at \"${where}\": ${\n first?.message ?? 'schema violation'\n }`,\n `Correct ${configPath} so it matches the ekanos.json schema ` +\n `({ slug, entry } or { integrations: [...] }, plus optional ` +\n `source, host and sourceGlobs).`,\n );\n }\n\n const project = result.data;\n\n const integrations = projectIntegrations(project).map((declared) => {\n const entryPath = path.resolve(projectDir, declared.entry);\n if (!fs.existsSync(entryPath)) {\n throw notFoundError(\n `entry \"${declared.entry}\" for \"${declared.slug}\" resolves to ` +\n `${entryPath}, which does not exist.`,\n `Point it at your integration module (the file that exports the ` +\n `defineIntegration(...) result) in ${configPath}.`,\n );\n }\n return { slug: declared.slug, entry: declared.entry, entryPath };\n });\n\n assertEsmProject(projectDir);\n\n return {\n project,\n projectDir,\n configPath,\n integrations,\n primary: integrations[0]!,\n };\n}\n\n/**\n * Fail unless the project is an ES module package.\n *\n * `init` sets `\"type\": \"module\"` and says so, but a warning can be scrolled\n * past, and a partner who misses it gets a confusing module-resolution error\n * from deep inside Node or Next rather than a sentence naming the problem.\n * This is the backstop: one precondition, in the loader every verb after\n * `init` goes through, so the failure is loud and says what to do.\n *\n * `init` deliberately does NOT come through here — it is the thing that fixes\n * this state.\n *\n * A project with no package.json at all is left alone: `dev` reports missing\n * dependencies with its own better message, and `validate`/`test` have their\n * own preconditions. We only assert on a manifest that exists and disagrees.\n */\nexport function assertEsmProject(projectDir: string): void {\n const manifestPath = path.join(projectDir, 'package.json');\n if (!fs.existsSync(manifestPath)) return;\n\n let manifest: unknown;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n } catch {\n // Malformed JSON is npm's problem to report, not ours to guess at; the\n // partner will hit it on their next install with a better message.\n return;\n }\n\n const declared =\n typeof manifest === 'object' && manifest !== null\n ? (manifest as { type?: unknown }).type\n : undefined;\n\n if (declared === 'module') return;\n\n throw preconditionError(\n declared === undefined\n ? `${manifestPath} does not declare \"type\": \"module\", so Node treats ` +\n 'your integration as CommonJS and cannot load it.'\n : `${manifestPath} declares \"type\": ${JSON.stringify(declared)}, but the ` +\n 'integration sources are ES modules and Node cannot load them.',\n `Set \"type\": \"module\" in ${manifestPath}. If this project has CommonJS ` +\n 'sources of its own, put your integration in its own package instead — ' +\n 'the two module systems cannot share one package.json. Re-running ' +\n '\"ekanos init\" also fixes this.',\n );\n}\n\n/** Serialize an ekanos.json body with a trailing newline. */\nexport function serializeProject(project: EkanosProject): string {\n return `${JSON.stringify(project, null, 2)}\\n`;\n}\n"]}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nimport { notFoundError, preconditionError } from './errors';\n\nconst SlugSchema = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {\n message:\n 'slug must be kebab-case ([a-z0-9] segments separated by single hyphens), e.g. \"acme-crm\".',\n});\n\nconst EntrySchema = z\n .string()\n .min(1, { message: 'entry must be a path to the integration module.' });\n\n/**\n * Extra Tailwind content globs, for an integration that does not live under\n * the default layout. They are interpolated into the generated\n * `styles/globals.css` as `@source` lines, so they are validated as\n * DATA-IN-CSS: project-relative, no escaping the project, and none of the\n * characters that could close the string or the surrounding comment.\n *\n * Being strict here is deliberate. A glob that matches nothing costs a partner\n * a silently unstyled integration; a glob that is rejected costs them one\n * clear error.\n */\nconst SOURCE_GLOB_PATTERN = /^[A-Za-z0-9_\\-./*{},[\\]!]+$/;\n\nconst SourceGlobSchema = z\n .string()\n .min(1, { message: 'a sourceGlobs entry must not be empty.' })\n .max(200, { message: 'a sourceGlobs entry must be under 200 characters.' })\n .regex(SOURCE_GLOB_PATTERN, {\n message:\n 'a sourceGlobs entry may contain only letters, digits and _-./*{},[]! ' +\n '— it is written verbatim into the generated stylesheet.',\n })\n .refine((glob) => !glob.startsWith('/'), {\n message: 'a sourceGlobs entry must be relative to the project root.',\n })\n .refine((glob) => !glob.split('/').includes('..'), {\n message: 'a sourceGlobs entry must not escape the project root with \"..\".',\n });\n\n/** Anything with a scheme prefix — `https:`, `file:`, a Windows drive `C:`. */\nconst URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:/i;\n\n/** True when a `docs` entry is a URL (never touched by local checks). */\nexport function isDocUrl(entry: string): boolean {\n return /^https?:\\/\\//i.test(entry);\n}\n\n/**\n * Why a `docs` entry is invalid, or null when it is fine. One rule, shared by\n * the schema below and `init --docs`, so \"valid on the command line\" and\n * \"valid in ekanos.json\" cannot drift apart: an entry is either an http(s)\n * URL or a path relative to the project root. Absolute paths are rejected\n * because they are meaningless on anyone else's machine, and non-http(s)\n * schemes because nothing here will ever dereference them.\n */\nexport function docEntryProblem(entry: string): string | null {\n if (entry.trim() === '') return 'a docs entry must not be empty.';\n\n if (URL_SCHEME_PATTERN.test(entry)) {\n if (!isDocUrl(entry)) {\n return (\n `\"${entry}\" is neither an http(s) URL nor a relative path — a docs ` +\n 'entry is a URL to API documentation or a path relative to the ' +\n 'project root.'\n );\n }\n try {\n new URL(entry);\n return null;\n } catch {\n return `\"${entry}\" is not a valid http(s) URL.`;\n }\n }\n\n if (path.isAbsolute(entry)) {\n return (\n `\"${entry}\" is an absolute path — a docs entry that is a file must be ` +\n 'relative to the project root, so it means the same thing on every ' +\n 'machine that clones the project.'\n );\n }\n\n // Same rule as SourceGlobSchema, for the same reason: a `..` segment lets a\n // relative entry escape the project root, and `validate` resolves these\n // paths for the missing-file check.\n if (entry.split(/[\\\\/]/).includes('..')) {\n return (\n `\"${entry}\" escapes the project root with \"..\" — a docs entry that is ` +\n 'a file must stay inside the project.'\n );\n }\n\n return null;\n}\n\n/**\n * One `docs` entry: where the target API's documentation lives. An http(s)\n * URL (an OpenAPI spec, the official docs site) or a project-relative path\n * (a vendored spec, `docs/api-notes.md`). Local paths are advisory-checked by\n * `validate` (`docs/missing-file`, a warning); URLs are never fetched.\n */\nconst DocEntrySchema = z.string().superRefine((entry, ctx) => {\n const problem = docEntryProblem(entry);\n if (problem) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem });\n }\n});\n\n/** One integration: the pair that addresses a definition module. */\nconst IntegrationEntrySchema = z\n .object({ slug: SlugSchema, entry: EntrySchema })\n .strict();\n\nexport type IntegrationEntry = z.infer<typeof IntegrationEntrySchema>;\n\n/**\n * `ekanos.json` — the project contract. Written by `init`, read by `validate`,\n * `dev` and `test`.\n *\n * Two shapes, because a project can hold more than one integration and the\n * single-integration form is what `init` writes and what most projects keep:\n *\n * ```json\n * { \"slug\": \"acme-crm\", \"entry\": \"src/integration.ts\" }\n *\n * { \"integrations\": [\n * { \"slug\": \"acme-crm\", \"entry\": \"src/crm.ts\" },\n * { \"slug\": \"acme-billing\", \"entry\": \"src/billing.ts\" }\n * ] }\n * ```\n *\n * A second integration used to be invisible to the CLI entirely — the harness\n * registry is an array and `collectCollisionFindings` exists precisely to\n * cross-check several definitions against each other, but `validate` could\n * only ever see one. Both forms normalise to the same list, so nothing\n * downstream has to know which was written.\n */\n/**\n * The Fusion source this project publishes to, by slug. Fusion source slugs\n * are opaque server-side identifiers, so the only local claims are \"one\n * token, sanely sized\" — the server resolves and authorizes it.\n */\nconst SourceSlugSchema = z\n .string()\n .min(1, { message: 'source must not be empty.' })\n .max(255, { message: 'source must be at most 255 characters.' })\n .regex(/^\\S+$/, { message: 'source must not contain whitespace.' });\n\n/**\n * The Fusion host this project publishes to. Origin-shaped: the same\n * normalization the credential store applies (`normalizeHost`) accepts it, so\n * \"valid here\" and \"usable as a session key\" cannot drift apart.\n */\nconst HostSchema = z\n .string()\n .min(1, { message: 'host must not be empty.' })\n .max(255, { message: 'host must be at most 255 characters.' })\n .refine(\n (value) => {\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:';\n } catch {\n return false;\n }\n },\n {\n message:\n 'host must be a full http(s) origin, e.g. \"https://app.example.com\".',\n },\n );\n\nexport const EkanosProjectSchema = z\n .object({\n $schema: z.string().optional(),\n slug: SlugSchema.optional(),\n entry: EntrySchema.optional(),\n integrations: z.array(IntegrationEntrySchema).optional(),\n /**\n * Written by the first successful `ekanos publish --source <slug>` so\n * later publishes need no flag. Read only by `publish`.\n */\n source: SourceSlugSchema.optional(),\n /**\n * The Fusion host this project publishes to — an http(s) origin, e.g.\n * \"https://app.example.com\". Written by the first successful `ekanos\n * publish` (alongside `source`), read by every host-taking verb as the\n * fallback after `--host` and `EKANOS_HOST`.\n */\n host: HostSchema.optional(),\n /**\n * Where the target API's documentation lives — http(s) URLs and\n * project-relative paths, in the order an agent should try them. Written\n * by `init --docs`, read by agents (see AGENTS.md's \"API documentation\")\n * and advisory-checked by `validate`. Optional and allowed empty: a\n * project that predates the field stays valid.\n */\n docs: z.array(DocEntrySchema).optional(),\n // There is deliberately no `harness` field. One existed, accepted by the\n // schema and read by nothing — the same accept-and-ignore that made\n // `sourceGlobs` cost a partner a silently unstyled integration. `.strict()`\n // now rejects it by name, which is the honest answer until something\n // actually consumes it.\n sourceGlobs: z.array(SourceGlobSchema).optional(),\n })\n .strict()\n .superRefine((value, ctx) => {\n const hasSingle = value.slug !== undefined || value.entry !== undefined;\n const hasList = value.integrations !== undefined;\n\n if (hasSingle && hasList) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'use either the single form ({ slug, entry }) or the list form ' +\n '({ integrations: [...] }), not both — two places to declare the ' +\n 'same thing is how they drift apart.',\n });\n return;\n }\n\n if (hasList) {\n if (value.integrations!.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations'],\n message: 'integrations must list at least one integration.',\n });\n }\n const seen = new Set<string>();\n value.integrations!.forEach((entry, index) => {\n if (seen.has(entry.slug)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['integrations', index, 'slug'],\n message:\n `duplicate slug \"${entry.slug}\" — every integration in a ` +\n 'project needs its own, since the slug addresses it everywhere.',\n });\n }\n seen.add(entry.slug);\n });\n return;\n }\n\n if (value.slug === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slug'],\n message: 'slug is required (or use \"integrations\": [...]).',\n });\n }\n if (value.entry === undefined) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['entry'],\n message: 'entry is required (or use \"integrations\": [...]).',\n });\n }\n });\n\nexport type EkanosProject = z.infer<typeof EkanosProjectSchema>;\n\n/** Both `ekanos.json` shapes, flattened to the one the CLI works in. */\nexport function projectIntegrations(\n project: EkanosProject,\n): IntegrationEntry[] {\n if (project.integrations !== undefined) return [...project.integrations];\n return [{ slug: project.slug!, entry: project.entry! }];\n}\n\nexport const EKANOS_CONFIG_FILENAME = 'ekanos.json';\n\n/** One integration from `ekanos.json`, with its entry resolved on disk. */\nexport interface LoadedIntegration {\n slug: string;\n entry: string;\n /** Absolute path to the resolved entry module. */\n entryPath: string;\n}\n\nexport interface LoadedProject {\n project: EkanosProject;\n /** Absolute path to the directory containing ekanos.json (the project root). */\n projectDir: string;\n /** Absolute path to ekanos.json itself. */\n configPath: string;\n /** Every integration the project declares, in declaration order. */\n integrations: LoadedIntegration[];\n /**\n * The first integration. It names the generated harness shell and is what a\n * single-integration project means by \"the\" integration; nothing else\n * privileges it.\n */\n primary: LoadedIntegration;\n}\n\n/**\n * Read and validate ekanos.json from a directory. Missing file → not-found\n * (exit 6); malformed JSON or a schema violation → precondition failed\n * (exit 9), because a project the CLI cannot even read is a precondition for\n * every verb, not a definition-level validation finding.\n */\nexport function loadProject(dir: string): LoadedProject {\n const projectDir = path.resolve(dir);\n const configPath = path.join(projectDir, EKANOS_CONFIG_FILENAME);\n\n if (!fs.existsSync(configPath)) {\n throw notFoundError(\n `No ${EKANOS_CONFIG_FILENAME} found in ${projectDir}.`,\n `Run \"ekanos init\" here first, or change into the project directory ` +\n `that contains ${EKANOS_CONFIG_FILENAME}.`,\n );\n }\n\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n } catch (error) {\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is not valid JSON: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Fix the JSON syntax in ${configPath}.`,\n );\n }\n\n const result = EkanosProjectSchema.safeParse(raw);\n if (!result.success) {\n const first = result.error.issues[0];\n const where =\n first && first.path.length > 0 ? first.path.join('.') : '(root)';\n throw preconditionError(\n `${EKANOS_CONFIG_FILENAME} is invalid at \"${where}\": ${\n first?.message ?? 'schema violation'\n }`,\n `Correct ${configPath} so it matches the ekanos.json schema ` +\n `({ slug, entry } or { integrations: [...] }, plus optional ` +\n `source, host, docs and sourceGlobs).`,\n );\n }\n\n const project = result.data;\n\n const integrations = projectIntegrations(project).map((declared) => {\n const entryPath = path.resolve(projectDir, declared.entry);\n if (!fs.existsSync(entryPath)) {\n throw notFoundError(\n `entry \"${declared.entry}\" for \"${declared.slug}\" resolves to ` +\n `${entryPath}, which does not exist.`,\n `Point it at your integration module (the file that exports the ` +\n `defineIntegration(...) result) in ${configPath}.`,\n );\n }\n return { slug: declared.slug, entry: declared.entry, entryPath };\n });\n\n assertEsmProject(projectDir);\n\n return {\n project,\n projectDir,\n configPath,\n integrations,\n primary: integrations[0]!,\n };\n}\n\n/**\n * Fail unless the project is an ES module package.\n *\n * `init` sets `\"type\": \"module\"` and says so, but a warning can be scrolled\n * past, and a partner who misses it gets a confusing module-resolution error\n * from deep inside Node or Next rather than a sentence naming the problem.\n * This is the backstop: one precondition, in the loader every verb after\n * `init` goes through, so the failure is loud and says what to do.\n *\n * `init` deliberately does NOT come through here — it is the thing that fixes\n * this state.\n *\n * A project with no package.json at all is left alone: `dev` reports missing\n * dependencies with its own better message, and `validate`/`test` have their\n * own preconditions. We only assert on a manifest that exists and disagrees.\n */\nexport function assertEsmProject(projectDir: string): void {\n const manifestPath = path.join(projectDir, 'package.json');\n if (!fs.existsSync(manifestPath)) return;\n\n let manifest: unknown;\n try {\n manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n } catch {\n // Malformed JSON is npm's problem to report, not ours to guess at; the\n // partner will hit it on their next install with a better message.\n return;\n }\n\n const declared =\n typeof manifest === 'object' && manifest !== null\n ? (manifest as { type?: unknown }).type\n : undefined;\n\n if (declared === 'module') return;\n\n throw preconditionError(\n declared === undefined\n ? `${manifestPath} does not declare \"type\": \"module\", so Node treats ` +\n 'your integration as CommonJS and cannot load it.'\n : `${manifestPath} declares \"type\": ${JSON.stringify(declared)}, but the ` +\n 'integration sources are ES modules and Node cannot load them.',\n `Set \"type\": \"module\" in ${manifestPath}. If this project has CommonJS ` +\n 'sources of its own, put your integration in its own package instead — ' +\n 'the two module systems cannot share one package.json. Re-running ' +\n '\"ekanos init\" also fixes this.',\n );\n}\n\n/** Serialize an ekanos.json body with a trailing newline. */\nexport function serializeProject(project: EkanosProject): string {\n return `${JSON.stringify(project, null, 2)}\\n`;\n}\n"]}
@@ -20,7 +20,7 @@
20
20
  * → 400 { error, code: "INVALID_REQUEST" | "INVALID_ARCHIVE" }
21
21
  * → 401 { error, code: "AUTH_REQUIRED" } token absent or expired —
22
22
  * refresh once and retry
23
- * → 403 { error, code: "FORBIDDEN" } no dev seat on the source
23
+ * → 403 { error, code: "FORBIDDEN" } no developer seat on the source
24
24
  * → 404 { error, code: "SOURCE_NOT_FOUND" }
25
25
  * → 409 { error, code: "VERSION_EXISTS" } this (slug, version) is
26
26
  * already submitted
@@ -29,15 +29,26 @@
29
29
  * auth-gated Fusion page
30
30
  * answers 307)
31
31
  *
32
- * GET /api/partner/submissions?source=<slug|id> [Authorization: Bearer]
32
+ * GET /api/partner/submissions?source=<slug|id>[&detail=findings]
33
+ * [Authorization: Bearer]
33
34
  * → 200 { ok: true, data: { source, integrations: [{ id, slug, name,
34
35
  * createdAt, versions: [{ id, version, state, archiveSha256,
35
- * submittedAt }] }] } }
36
+ * submittedAt, gatedAt,
37
+ * // present once a gate has run (gate_report is non-null):
38
+ * errorCount?, warnCount?,
39
+ * // present ONLY with ?detail=findings, alongside the above:
40
+ * findings?, stages?
41
+ * }] }] } }
36
42
  * → 400 { error, code: "INVALID_REQUEST" }
37
43
  * → 401 { error, code: "AUTH_REQUIRED" } refresh once and retry
38
44
  * → 403 { error, code: "MFA_REQUIRED" }
39
45
  * → 404 { error, code: "SOURCE_NOT_FOUND" }
40
46
  * → 3xx treated as 401
47
+ *
48
+ * `state` walks submitted -> gated|rejected -> dev -> prod -> revoked (see
49
+ * the trigger in apps/web/supabase/schemas/110-partner-submissions.sql for
50
+ * the full transition matrix). `gated`/`dev`/`rejected` are the
51
+ * terminal-for-waiting states `ekanos status --watch` polls for.
41
52
  * ---------------------------------------------------------------------------
42
53
  */
43
54
  /**
@@ -71,12 +82,39 @@ export type SubmitResult = {
71
82
  status: 'unauthorized';
72
83
  };
73
84
  export declare function submitArchive(host: string, accessToken: string, payload: SubmissionPayload): Promise<SubmitResult>;
85
+ /**
86
+ * One gate finding, mirroring `@ekanos/integration-schema`'s `Finding` shape
87
+ * (the same one `validate`/`publish` carry in `data.findings`). Declared
88
+ * locally rather than imported: the gate's own report type lives in
89
+ * `@ekanos/checks`, a server-side/toolchain package this partner-facing CLI
90
+ * has no business depending on — the wire shape is the only contract.
91
+ */
92
+ export interface GateFinding {
93
+ check: string;
94
+ severity: 'error' | 'warn' | 'info';
95
+ file?: string;
96
+ line?: number;
97
+ message: string;
98
+ hint: string;
99
+ }
100
+ export interface GateStageResult {
101
+ id: string;
102
+ status: 'pass' | 'fail' | 'skipped';
103
+ durationMs: number;
104
+ }
74
105
  export interface SubmissionVersion {
75
106
  id: string;
76
107
  version: string;
77
108
  state: string;
78
109
  archiveSha256: string | null;
79
110
  submittedAt: string | null;
111
+ gatedAt: string | null;
112
+ /** Present once a gate has run. */
113
+ errorCount: number | null;
114
+ warnCount: number | null;
115
+ /** Present only when the request passed `?detail=findings`. */
116
+ findings?: GateFinding[];
117
+ stages?: GateStageResult[];
80
118
  }
81
119
  export interface SubmissionListing {
82
120
  id: string;
@@ -97,7 +135,13 @@ export type ListResult = {
97
135
  * List the caller's visible submissions for a source. Same authorization model
98
136
  * as `submitArchive`: the seat SELECT policies filter server-side, so a caller
99
137
  * with no seat sees an empty list rather than an error.
138
+ *
139
+ * `detail: 'findings'` requests the full per-version findings/stages arrays
140
+ * (used by `status --findings` and `status --watch`); omitted, the server
141
+ * answers with counts only — see the wire contract above.
100
142
  */
101
- export declare function listSubmissions(host: string, accessToken: string, source: string): Promise<ListResult>;
143
+ export declare function listSubmissions(host: string, accessToken: string, source: string, options?: {
144
+ detail?: 'findings';
145
+ }): Promise<ListResult>;
102
146
  /** Guard for the "still unauthorized after one refresh" terminal case. */
103
147
  export declare function stillUnauthorized(host: string): never;
@@ -22,7 +22,7 @@ import { authRequiredError, forbiddenError, invalidStateError, networkError, not
22
22
  * → 400 { error, code: "INVALID_REQUEST" | "INVALID_ARCHIVE" }
23
23
  * → 401 { error, code: "AUTH_REQUIRED" } token absent or expired —
24
24
  * refresh once and retry
25
- * → 403 { error, code: "FORBIDDEN" } no dev seat on the source
25
+ * → 403 { error, code: "FORBIDDEN" } no developer seat on the source
26
26
  * → 404 { error, code: "SOURCE_NOT_FOUND" }
27
27
  * → 409 { error, code: "VERSION_EXISTS" } this (slug, version) is
28
28
  * already submitted
@@ -31,15 +31,26 @@ import { authRequiredError, forbiddenError, invalidStateError, networkError, not
31
31
  * auth-gated Fusion page
32
32
  * answers 307)
33
33
  *
34
- * GET /api/partner/submissions?source=<slug|id> [Authorization: Bearer]
34
+ * GET /api/partner/submissions?source=<slug|id>[&detail=findings]
35
+ * [Authorization: Bearer]
35
36
  * → 200 { ok: true, data: { source, integrations: [{ id, slug, name,
36
37
  * createdAt, versions: [{ id, version, state, archiveSha256,
37
- * submittedAt }] }] } }
38
+ * submittedAt, gatedAt,
39
+ * // present once a gate has run (gate_report is non-null):
40
+ * errorCount?, warnCount?,
41
+ * // present ONLY with ?detail=findings, alongside the above:
42
+ * findings?, stages?
43
+ * }] }] } }
38
44
  * → 400 { error, code: "INVALID_REQUEST" }
39
45
  * → 401 { error, code: "AUTH_REQUIRED" } refresh once and retry
40
46
  * → 403 { error, code: "MFA_REQUIRED" }
41
47
  * → 404 { error, code: "SOURCE_NOT_FOUND" }
42
48
  * → 3xx treated as 401
49
+ *
50
+ * `state` walks submitted -> gated|rejected -> dev -> prod -> revoked (see
51
+ * the trigger in apps/web/supabase/schemas/110-partner-submissions.sql for
52
+ * the full transition matrix). `gated`/`dev`/`rejected` are the
53
+ * terminal-for-waiting states `ekanos status --watch` polls for.
43
54
  * ---------------------------------------------------------------------------
44
55
  */
45
56
  /**
@@ -77,7 +88,7 @@ export async function submitArchive(host, accessToken, payload) {
77
88
  const serverMessage = str(body.error);
78
89
  if (response.status === 403) {
79
90
  throw forbiddenError(serverMessage !== null && serverMessage !== void 0 ? serverMessage : `${host} refused the submission: this account does not hold the ` +
80
- `dev seat on source "${payload.source}".`, `Ask the source operator to grant your account the developer seat for ` +
91
+ `developer seat on source "${payload.source}".`, `Ask the source operator to grant your account the developer seat for ` +
81
92
  `"${payload.source}", then retry.`);
82
93
  }
83
94
  if (response.status === 404) {
@@ -118,10 +129,17 @@ export async function submitArchive(host, accessToken, payload) {
118
129
  * List the caller's visible submissions for a source. Same authorization model
119
130
  * as `submitArchive`: the seat SELECT policies filter server-side, so a caller
120
131
  * with no seat sees an empty list rather than an error.
132
+ *
133
+ * `detail: 'findings'` requests the full per-version findings/stages arrays
134
+ * (used by `status --findings` and `status --watch`); omitted, the server
135
+ * answers with counts only — see the wire contract above.
121
136
  */
122
- export async function listSubmissions(host, accessToken, source) {
137
+ export async function listSubmissions(host, accessToken, source, options = {}) {
123
138
  var _a;
124
- const response = await request(`${host}/api/partner/submissions?source=${encodeURIComponent(source)}`, {
139
+ const query = new URLSearchParams({ source });
140
+ if (options.detail)
141
+ query.set('detail', options.detail);
142
+ const response = await request(`${host}/api/partner/submissions?${query.toString()}`, {
125
143
  method: 'GET',
126
144
  headers: {
127
145
  accept: 'application/json',
@@ -166,13 +184,11 @@ export async function listSubmissions(host, accessToken, source) {
166
184
  .filter((version) => typeof version === 'object' && version !== null)
167
185
  .map((version) => {
168
186
  var _a, _b, _c;
169
- return ({
170
- id: (_a = str(version.id)) !== null && _a !== void 0 ? _a : '',
171
- version: (_b = str(version.version)) !== null && _b !== void 0 ? _b : '',
172
- state: (_c = str(version.state)) !== null && _c !== void 0 ? _c : '',
173
- archiveSha256: str(version.archiveSha256),
174
- submittedAt: str(version.submittedAt),
175
- });
187
+ return (Object.assign(Object.assign({ id: (_a = str(version.id)) !== null && _a !== void 0 ? _a : '', version: (_b = str(version.version)) !== null && _b !== void 0 ? _b : '', state: (_c = str(version.state)) !== null && _c !== void 0 ? _c : '', archiveSha256: str(version.archiveSha256), submittedAt: str(version.submittedAt), gatedAt: str(version.gatedAt), errorCount: num(version.errorCount), warnCount: num(version.warnCount) }, (Array.isArray(version.findings)
188
+ ? { findings: version.findings }
189
+ : {})), (Array.isArray(version.stages)
190
+ ? { stages: version.stages }
191
+ : {})));
176
192
  }),
177
193
  });
178
194
  });
@@ -199,4 +215,7 @@ function isRedirect(status) {
199
215
  function str(value) {
200
216
  return typeof value === 'string' && value.length > 0 ? value : null;
201
217
  }
218
+ function num(value) {
219
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
220
+ }
202
221
  //# sourceMappingURL=publish-api.js.map