@ekanos/cli 0.3.6 → 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.
Files changed (50) hide show
  1. package/README.md +4 -0
  2. package/dist/commands/dev.js +5 -2
  3. package/dist/commands/dev.js.map +1 -1
  4. package/dist/commands/init.js +12 -1
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/commands/upgrade.js +58 -7
  7. package/dist/commands/upgrade.js.map +1 -1
  8. package/dist/compatibility.d.ts +4 -1
  9. package/dist/compatibility.js +4 -1
  10. package/dist/compatibility.js.map +1 -1
  11. package/dist/context.d.ts +2 -0
  12. package/dist/context.js +5 -5
  13. package/dist/context.js.map +1 -1
  14. package/dist/design-findings.d.ts +22 -0
  15. package/dist/design-findings.js +47 -0
  16. package/dist/design-findings.js.map +1 -0
  17. package/dist/docs-links.d.ts +20 -0
  18. package/dist/docs-links.js +33 -0
  19. package/dist/docs-links.js.map +1 -0
  20. package/dist/errors.d.ts +4 -1
  21. package/dist/errors.js +5 -1
  22. package/dist/errors.js.map +1 -1
  23. package/dist/findings-renderer.js +5 -0
  24. package/dist/findings-renderer.js.map +1 -1
  25. package/dist/harness-scaffold.d.ts +4 -0
  26. package/dist/harness-scaffold.js +18 -1
  27. package/dist/harness-scaffold.js.map +1 -1
  28. package/dist/index.js +3 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/pnpm-builds.d.ts +41 -6
  31. package/dist/pnpm-builds.js +169 -0
  32. package/dist/pnpm-builds.js.map +1 -1
  33. package/dist/schema-skew.js +2 -1
  34. package/dist/schema-skew.js.map +1 -1
  35. package/dist/validate-findings.js +2 -0
  36. package/dist/validate-findings.js.map +1 -1
  37. package/package.json +3 -2
  38. package/templates/AGENTS.md.tmpl +77 -9
  39. package/templates/CLAUDE.md.tmpl +5 -0
  40. package/templates/claude-skill.md.tmpl +7 -0
  41. package/templates/eslint.config.mjs.tmpl +62 -0
  42. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -0
  43. package/templates/harness.config.entry.ts.tmpl +8 -0
  44. package/templates/mcp.json.tmpl +4 -0
  45. package/templates/package.json.tmpl +7 -4
  46. package/templates/skills/build-integration/SKILL.md +11 -0
  47. package/templates/skills/build-integration/references/widget-design.md +128 -32
  48. package/templates/skills/polish-widgets/SKILL.md +35 -13
  49. package/templates/skills/polish-widgets/references/patterns.md +90 -58
  50. package/templates/src/integration.ts.tmpl +50 -6
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The public Ekanos SDK docs. Every docs URL the CLI prints or emits is built
3
+ * from this one constant, so a move of the site is a one-line change.
4
+ */
5
+ export const SDK_DOCS_URL = 'https://sdk.ekanos.dev';
6
+ /** A page on the docs site, e.g. `docsPage('cli/commands')`. */
7
+ export function docsPage(page, anchor) {
8
+ return `${SDK_DOCS_URL}/${page}${anchor ? `#${anchor}` : ''}`;
9
+ }
10
+ const COMMON_ERRORS = 'troubleshooting/common-errors';
11
+ /**
12
+ * The common-errors entries for the CLI error codes the docs document. Anchors
13
+ * are the headings under "CLI errors" in common-errors.mdx.
14
+ */
15
+ export const ERROR_DOCS = {
16
+ auth_required: docsPage(COMMON_ERRORS, 'exit-4-auth_required'),
17
+ forbidden: docsPage(COMMON_ERRORS, 'exit-5-forbidden'),
18
+ gate_failed: docsPage(COMMON_ERRORS, 'exit-10-gate_failed'),
19
+ };
20
+ /** The schema-skew entry: documented under `precondition_failed`, but only for skew. */
21
+ export const SCHEMA_SKEW_DOCS = docsPage(COMMON_ERRORS, 'exit-9-precondition_failed-schema-skew');
22
+ /**
23
+ * The validation findings the common-errors page documents, by `check` id.
24
+ * Anchors are the headings under "Validation findings". A check absent here
25
+ * has no docs entry, so the renderer prints no link rather than a dead one.
26
+ */
27
+ export const FINDING_DOCS = {
28
+ 'project.host-compiler-rules': docsPage(COMMON_ERRORS, 'project-host-compiler-rules-warning'),
29
+ 'project.slug-agreement': docsPage(COMMON_ERRORS, 'project-slug-agreement-error'),
30
+ 'icons/pro-only': docsPage(COMMON_ERRORS, 'icons/pro-only-warning'),
31
+ 'collision.widget-id': docsPage(COMMON_ERRORS, 'collision-widget-id-error'),
32
+ };
33
+ //# sourceMappingURL=docs-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-links.js","sourceRoot":"","sources":["../src/docs-links.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAErD,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAe;IACpD,OAAO,GAAG,YAAY,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,aAAa,GAAG,+BAA+B,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAqC;IAC1D,aAAa,EAAE,QAAQ,CAAC,aAAa,EAAE,sBAAsB,CAAC;IAC9D,SAAS,EAAE,QAAQ,CAAC,aAAa,EAAE,kBAAkB,CAAC;IACtD,WAAW,EAAE,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC;CAC5D,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,gBAAgB,GAAG,QAAQ,CACtC,aAAa,EACb,wCAAwC,CACzC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAqC;IAC5D,6BAA6B,EAAE,QAAQ,CACrC,aAAa,EACb,qCAAqC,CACtC;IACD,wBAAwB,EAAE,QAAQ,CAChC,aAAa,EACb,8BAA8B,CAC/B;IACD,gBAAgB,EAAE,QAAQ,CAAC,aAAa,EAAE,wBAAwB,CAAC;IACnE,qBAAqB,EAAE,QAAQ,CAAC,aAAa,EAAE,2BAA2B,CAAC;CAC5E,CAAC","sourcesContent":["/**\n * The public Ekanos SDK docs. Every docs URL the CLI prints or emits is built\n * from this one constant, so a move of the site is a one-line change.\n */\nexport const SDK_DOCS_URL = 'https://sdk.ekanos.dev';\n\n/** A page on the docs site, e.g. `docsPage('cli/commands')`. */\nexport function docsPage(page: string, anchor?: string): string {\n return `${SDK_DOCS_URL}/${page}${anchor ? `#${anchor}` : ''}`;\n}\n\nconst COMMON_ERRORS = 'troubleshooting/common-errors';\n\n/**\n * The common-errors entries for the CLI error codes the docs document. Anchors\n * are the headings under \"CLI errors\" in common-errors.mdx.\n */\nexport const ERROR_DOCS: Readonly<Record<string, string>> = {\n auth_required: docsPage(COMMON_ERRORS, 'exit-4-auth_required'),\n forbidden: docsPage(COMMON_ERRORS, 'exit-5-forbidden'),\n gate_failed: docsPage(COMMON_ERRORS, 'exit-10-gate_failed'),\n};\n\n/** The schema-skew entry: documented under `precondition_failed`, but only for skew. */\nexport const SCHEMA_SKEW_DOCS = docsPage(\n COMMON_ERRORS,\n 'exit-9-precondition_failed-schema-skew',\n);\n\n/**\n * The validation findings the common-errors page documents, by `check` id.\n * Anchors are the headings under \"Validation findings\". A check absent here\n * has no docs entry, so the renderer prints no link rather than a dead one.\n */\nexport const FINDING_DOCS: Readonly<Record<string, string>> = {\n 'project.host-compiler-rules': docsPage(\n COMMON_ERRORS,\n 'project-host-compiler-rules-warning',\n ),\n 'project.slug-agreement': docsPage(\n COMMON_ERRORS,\n 'project-slug-agreement-error',\n ),\n 'icons/pro-only': docsPage(COMMON_ERRORS, 'icons/pro-only-warning'),\n 'collision.widget-id': docsPage(COMMON_ERRORS, 'collision-widget-id-error'),\n};\n"]}
package/dist/errors.d.ts CHANGED
@@ -11,11 +11,14 @@ export declare class CliError extends Error {
11
11
  readonly code: ErrorCode;
12
12
  readonly exitCode: ExitCode;
13
13
  readonly hint: string;
14
+ /** A docs URL for this error, when the docs document it. Absent otherwise. */
15
+ readonly docs?: string;
14
16
  constructor(params: {
15
17
  code: ErrorCode;
16
18
  exitCode: ExitCode;
17
19
  message: string;
18
20
  hint: string;
21
+ docs?: string;
19
22
  });
20
23
  }
21
24
  /** A usage error (unknown flag/command, bad argument). Exit 2. */
@@ -23,7 +26,7 @@ export declare function usageError(message: string, hint: string): CliError;
23
26
  /** Validation produced error-severity findings. Exit 3. */
24
27
  export declare function validationError(message: string, hint: string): CliError;
25
28
  /** A precondition failure (missing tool, config, or file). Exit 9. */
26
- export declare function preconditionError(message: string, hint: string): CliError;
29
+ export declare function preconditionError(message: string, hint: string, docs?: string): CliError;
27
30
  /** An invalid-state failure (idempotency / ordering). Exit 7. */
28
31
  export declare function invalidStateError(message: string, hint: string): CliError;
29
32
  /** A not-found failure. Exit 6. */
package/dist/errors.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ERROR_DOCS } from './docs-links.js';
1
2
  import { ERROR_CODES, EXIT_CODES, } from './exit-codes.js';
2
3
  /**
3
4
  * The one error type every command path throws. It carries the three things
@@ -9,6 +10,7 @@ import { ERROR_CODES, EXIT_CODES, } from './exit-codes.js';
9
10
  */
10
11
  export class CliError extends Error {
11
12
  constructor(params) {
13
+ var _a;
12
14
  super(params.message);
13
15
  this.name = 'CliError';
14
16
  if (!params.hint || params.hint.trim().length === 0) {
@@ -18,6 +20,7 @@ export class CliError extends Error {
18
20
  this.code = params.code;
19
21
  this.exitCode = params.exitCode;
20
22
  this.hint = params.hint;
23
+ this.docs = (_a = params.docs) !== null && _a !== void 0 ? _a : ERROR_DOCS[params.code];
21
24
  }
22
25
  }
23
26
  /** A usage error (unknown flag/command, bad argument). Exit 2. */
@@ -39,12 +42,13 @@ export function validationError(message, hint) {
39
42
  });
40
43
  }
41
44
  /** A precondition failure (missing tool, config, or file). Exit 9. */
42
- export function preconditionError(message, hint) {
45
+ export function preconditionError(message, hint, docs) {
43
46
  return new CliError({
44
47
  code: ERROR_CODES.PRECONDITION_FAILED,
45
48
  exitCode: EXIT_CODES.PRECONDITION_FAILED,
46
49
  message,
47
50
  hint,
51
+ docs,
48
52
  });
49
53
  }
50
54
  /** An invalid-state failure (idempotency / ordering). Exit 7. */
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ERROR_CODES,\n EXIT_CODES,\n type ErrorCode,\n type ExitCode,\n} from './exit-codes';\n\n/**\n * The one error type every command path throws. It carries the three things\n * the envelope needs — a stable string `code`, a human `message`, and a\n * non-empty imperative `hint` — plus the numeric `exitCode` the process ends\n * with. Constructing one with an empty hint is a programming error and throws\n * immediately, which is what lets `context.fail()` guarantee the envelope's\n * \"every error has a hint\" invariant without a runtime check at the edge.\n */\nexport class CliError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: ExitCode;\n readonly hint: string;\n\n constructor(params: {\n code: ErrorCode;\n exitCode: ExitCode;\n message: string;\n hint: string;\n }) {\n super(params.message);\n this.name = 'CliError';\n\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `CliError for \"${params.message}\" was constructed with an empty hint. ` +\n 'Every CLI error must carry an imperative remediation.',\n );\n }\n\n this.code = params.code;\n this.exitCode = params.exitCode;\n this.hint = params.hint;\n }\n}\n\n/** A usage error (unknown flag/command, bad argument). Exit 2. */\nexport function usageError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.USAGE,\n exitCode: EXIT_CODES.USAGE,\n message,\n hint,\n });\n}\n\n/** Validation produced error-severity findings. Exit 3. */\nexport function validationError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.VALIDATION,\n exitCode: EXIT_CODES.VALIDATION,\n message,\n hint,\n });\n}\n\n/** A precondition failure (missing tool, config, or file). Exit 9. */\nexport function preconditionError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.PRECONDITION_FAILED,\n exitCode: EXIT_CODES.PRECONDITION_FAILED,\n message,\n hint,\n });\n}\n\n/** An invalid-state failure (idempotency / ordering). Exit 7. */\nexport function invalidStateError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.INVALID_STATE,\n exitCode: EXIT_CODES.INVALID_STATE,\n message,\n hint,\n });\n}\n\n/** A not-found failure. Exit 6. */\nexport function notFoundError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NOT_FOUND,\n exitCode: EXIT_CODES.NOT_FOUND,\n message,\n hint,\n });\n}\n\n/** Authentication is required but absent or expired. Exit 4. */\nexport function authRequiredError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.AUTH_REQUIRED,\n exitCode: EXIT_CODES.AUTH_REQUIRED,\n message,\n hint,\n });\n}\n\n/** Authenticated, but the server refused. Exit 5. */\nexport function forbiddenError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.FORBIDDEN,\n exitCode: EXIT_CODES.FORBIDDEN,\n message,\n hint,\n });\n}\n\n/** A publish gate rejected the submission. Exit 10. */\nexport function gateFailedError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.GATE_FAILED,\n exitCode: EXIT_CODES.GATE_FAILED,\n message,\n hint,\n });\n}\n\n/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */\nexport function networkError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NETWORK,\n exitCode: EXIT_CODES.NETWORK,\n message,\n hint,\n });\n}\n\n/**\n * Coerce anything thrown into a CliError. A thrown CliError passes through; a\n * plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.\n */\nexport function toCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError({\n code: ERROR_CODES.INTERNAL,\n exitCode: EXIT_CODES.INTERNAL,\n message,\n hint:\n 'This is an internal CLI error. Re-run with --json to capture the full ' +\n 'envelope and report it at npm@govastly.com if it persists.',\n });\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import { ERROR_DOCS } from './docs-links';\nimport {\n ERROR_CODES,\n EXIT_CODES,\n type ErrorCode,\n type ExitCode,\n} from './exit-codes';\n\n/**\n * The one error type every command path throws. It carries the three things\n * the envelope needs — a stable string `code`, a human `message`, and a\n * non-empty imperative `hint` — plus the numeric `exitCode` the process ends\n * with. Constructing one with an empty hint is a programming error and throws\n * immediately, which is what lets `context.fail()` guarantee the envelope's\n * \"every error has a hint\" invariant without a runtime check at the edge.\n */\nexport class CliError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: ExitCode;\n readonly hint: string;\n /** A docs URL for this error, when the docs document it. Absent otherwise. */\n readonly docs?: string;\n\n constructor(params: {\n code: ErrorCode;\n exitCode: ExitCode;\n message: string;\n hint: string;\n docs?: string;\n }) {\n super(params.message);\n this.name = 'CliError';\n\n if (!params.hint || params.hint.trim().length === 0) {\n throw new Error(\n `CliError for \"${params.message}\" was constructed with an empty hint. ` +\n 'Every CLI error must carry an imperative remediation.',\n );\n }\n\n this.code = params.code;\n this.exitCode = params.exitCode;\n this.hint = params.hint;\n this.docs = params.docs ?? ERROR_DOCS[params.code];\n }\n}\n\n/** A usage error (unknown flag/command, bad argument). Exit 2. */\nexport function usageError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.USAGE,\n exitCode: EXIT_CODES.USAGE,\n message,\n hint,\n });\n}\n\n/** Validation produced error-severity findings. Exit 3. */\nexport function validationError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.VALIDATION,\n exitCode: EXIT_CODES.VALIDATION,\n message,\n hint,\n });\n}\n\n/** A precondition failure (missing tool, config, or file). Exit 9. */\nexport function preconditionError(\n message: string,\n hint: string,\n docs?: string,\n): CliError {\n return new CliError({\n code: ERROR_CODES.PRECONDITION_FAILED,\n exitCode: EXIT_CODES.PRECONDITION_FAILED,\n message,\n hint,\n docs,\n });\n}\n\n/** An invalid-state failure (idempotency / ordering). Exit 7. */\nexport function invalidStateError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.INVALID_STATE,\n exitCode: EXIT_CODES.INVALID_STATE,\n message,\n hint,\n });\n}\n\n/** A not-found failure. Exit 6. */\nexport function notFoundError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NOT_FOUND,\n exitCode: EXIT_CODES.NOT_FOUND,\n message,\n hint,\n });\n}\n\n/** Authentication is required but absent or expired. Exit 4. */\nexport function authRequiredError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.AUTH_REQUIRED,\n exitCode: EXIT_CODES.AUTH_REQUIRED,\n message,\n hint,\n });\n}\n\n/** Authenticated, but the server refused. Exit 5. */\nexport function forbiddenError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.FORBIDDEN,\n exitCode: EXIT_CODES.FORBIDDEN,\n message,\n hint,\n });\n}\n\n/** A publish gate rejected the submission. Exit 10. */\nexport function gateFailedError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.GATE_FAILED,\n exitCode: EXIT_CODES.GATE_FAILED,\n message,\n hint,\n });\n}\n\n/** A network operation failed (DNS, TLS, timeout, 5xx). Exit 8. */\nexport function networkError(message: string, hint: string): CliError {\n return new CliError({\n code: ERROR_CODES.NETWORK,\n exitCode: EXIT_CODES.NETWORK,\n message,\n hint,\n });\n}\n\n/**\n * Coerce anything thrown into a CliError. A thrown CliError passes through; a\n * plain Error becomes an INTERNAL (exit 1) with a generic-but-non-empty hint.\n */\nexport function toCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n\n const message = error instanceof Error ? error.message : String(error);\n return new CliError({\n code: ERROR_CODES.INTERNAL,\n exitCode: EXIT_CODES.INTERNAL,\n message,\n hint:\n 'This is an internal CLI error. Re-run with --json to capture the full ' +\n 'envelope and report it at npm@govastly.com if it persists.',\n });\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { FINDING_DOCS } from './docs-links.js';
3
4
  import { ENCLOSING_NOTE } from './source-locator.js';
4
5
  // Compile-time proof the two real shapes this renders are both assignable.
5
6
  const _typeCheck = (f) => f;
@@ -199,6 +200,10 @@ function buildGroupBody(ui, group, opts) {
199
200
  if (dedupedHint !== null) {
200
201
  lines.push(` ${ui.hint(dedupedHint)}`);
201
202
  }
203
+ const docs = FINDING_DOCS[group.check];
204
+ if (docs !== undefined) {
205
+ lines.push(` ${ui.dim(`Docs: ${docs}`)}`);
206
+ }
202
207
  return lines;
203
208
  }
204
209
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"findings-renderer.js","sourceRoot":"","sources":["../src/findings-renderer.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 { CliContext } from './context';\nimport type { GateFinding } from './publish-api';\nimport { ENCLOSING_NOTE } from './source-locator';\nimport type { BadgeKind, Ui } from './ui';\n\n/**\n * The shared findings block: one rendering used by `validate` (a failed run),\n * `publish` (a gate refusal), and `status --findings`, so a partner sees the\n * exact same shape wherever a finding shows up. This is the ONLY findings\n * renderer in the package — nothing else may hand-format a `Finding`/\n * `GateFinding` array for a human.\n *\n * Deliberately structural rather than importing `Finding` or `GateFinding`\n * directly as the parameter type: both already satisfy this shape (their\n * extra/missing fields are all optional), and a structural type keeps this\n * module from caring which of the two ships that array.\n */\nexport interface RenderableFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire — see `GateFinding`. */\n origin?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n /**\n * Optional multi-line supplement rendered directly under the finding's\n * message — currently only `ekanos test`'s curated failures appendix sets\n * this (an assertion diff). Additive: neither `Finding` nor `GateFinding`\n * carries it, so every pre-existing caller renders byte-identically to\n * before. Lines are shown as-is except a leading `-`/`+` (unified-diff\n * style), which is colored red/green respectively.\n */\n detail?: string;\n}\n\n// Compile-time proof the two real shapes this renders are both assignable.\nconst _typeCheck: (f: Finding | GateFinding) => RenderableFinding = (f) => f;\nvoid _typeCheck;\n\nexport interface RenderFindingsOptions {\n /** Findings shown per group before an overflow line takes over. Default 10. */\n maxPerGroup?: number;\n /** Base for resolving a finding's `file` before reading it for a codeframe. Default `process.cwd()`. */\n cwd?: string;\n /** Injectable for `relativeTime` snapshot tests. Default `new Date()`. */\n now?: Date;\n}\n\nconst DEFAULT_MAX_PER_GROUP = 10;\n\nconst SEVERITY_RANK: Record<RenderableFinding['severity'], number> = {\n error: 0,\n warn: 1,\n info: 2,\n};\n\ninterface Group {\n check: string;\n findings: RenderableFinding[];\n /** Insertion order, for a stable tie-break when sorting groups. */\n order: number;\n}\n\n/**\n * `relativeTime('2026-09-01T10:00:00Z')` → `\"2h ago\"`. Deliberately coarse\n * (rounds to the nearest unit, one unit at a time) — this is a findings-list\n * badge, not an audit-log timestamp, and a partner scanning for \"is this\n * recent\" doesn't need sub-minute precision.\n */\nexport function relativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n\n const diffSec = Math.max(0, Math.round((now.getTime() - then) / 1000));\n\n if (diffSec < 5) return 'just now';\n if (diffSec < 60) return `${diffSec}s ago`;\n\n const diffMin = Math.round(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n\n const diffHour = Math.round(diffMin / 60);\n if (diffHour < 24) return `${diffHour}h ago`;\n\n const diffDay = Math.round(diffHour / 24);\n if (diffDay < 30) return `${diffDay}d ago`;\n\n const diffMonth = Math.round(diffDay / 30);\n if (diffMonth < 12) return `${diffMonth}mo ago`;\n\n const diffYear = Math.round(diffMonth / 12);\n return `${diffYear}y ago`;\n}\n\n/**\n * A message beginning with a zod-style dot-path (`components.widgets[2].id: …`,\n * the shape `collectDefinitionFindings` produces — see the `Finding` doc\n * comment in `@ekanos/integration-schema`). Captures the path and the rest\n * separately so the renderer can dim the path instead of shouting the whole\n * sentence in one uniform color.\n */\nconst DOT_PATH_MESSAGE =\n /^([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+|\\[\\d+\\])*): ([^]+)$/;\n\n/**\n * Render one finding's message: a leading dot-path identical to the\n * finding's own `file` is pure duplication (the path would just repeat what\n * `file:line` already told the reader) and is dropped; any other dot-path\n * prefix is kept but dimmed, so the sentence itself reads at full contrast.\n * A message with no dot-path prefix passes through untouched.\n *\n * A trailing `source-locator.ts` `ENCLOSING_NOTE` (plain text in the data —\n * see that module's doc comment on why it never carries color itself) is\n * peeled off before the dot-path handling above and dimmed on its own, then\n * reattached — so it stays visually secondary to the message itself in\n * exactly the case where the frame it accompanies points at the enclosing\n * object rather than the missing key.\n */\nfunction renderMessage(ui: Ui, finding: RenderableFinding): string {\n const enclosingSuffix = ` ${ENCLOSING_NOTE}`;\n const hasEnclosingNote = finding.message.endsWith(enclosingSuffix);\n const message = hasEnclosingNote\n ? finding.message.slice(0, -enclosingSuffix.length)\n : finding.message;\n\n const match = DOT_PATH_MESSAGE.exec(message);\n const rendered = (() => {\n if (!match) return message;\n\n const [, dotPath, rest] = match;\n if (dotPath === finding.file) return rest ?? message;\n\n return `${ui.dim(dotPath ?? '')}: ${rest ?? ''}`;\n })();\n\n return hasEnclosingNote ? `${rendered} ${ui.dim(ENCLOSING_NOTE)}` : rendered;\n}\n\nfunction severityColor(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): (text: string) => string {\n if (severity === 'error') return ui.err;\n if (severity === 'warn') return ui.warn;\n return ui.dim;\n}\n\nfunction severityGlyph(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): string {\n if (severity === 'warn') return ui.glyphs.warn;\n if (severity === 'info') return ui.glyphs.info;\n return ui.glyphs.err;\n}\n\nfunction ellipsis(ui: Ui): string {\n return ui.glyphs.ok === '✓' ? '…' : '...';\n}\n\n/** Attempts a codeframe for `finding`; `null` when it isn't renderable (no line, or the file can't be read locally). */\nfunction tryCodeframe(\n ui: Ui,\n finding: RenderableFinding,\n cwd: string,\n): string | null {\n if (finding.file === undefined || finding.line === undefined) return null;\n\n const resolved = path.resolve(cwd, finding.file);\n if (!fs.existsSync(resolved)) return null;\n\n try {\n const source = fs.readFileSync(resolved, 'utf8');\n return ui.codeframe({ file: finding.file, line: finding.line, source });\n } catch {\n // Best-effort decoration — an unreadable file (permissions, a race with\n // a concurrent edit) just means no codeframe, never a renderer failure.\n return null;\n }\n}\n\nfunction originBadgeKind(_origin: string): BadgeKind {\n return 'dim';\n}\n\n/**\n * Renders a finding's `detail` block, one line per source line: a\n * unified-diff `-` prefix is red, a `+` prefix is green, everything else\n * (context lines, \"Expected\"/\"Received\" headers, an overflow note) is dim.\n * `---`/`+++` file-header lines never appear here — `test-report.ts` never\n * emits them — but are guarded against anyway since this is the one place\n * that would mis-color them.\n */\nfunction renderDetailLines(ui: Ui, detail: string): string[] {\n return detail.split('\\n').map((line) => {\n if (line.startsWith('-') && !line.startsWith('---')) return ui.err(line);\n if (line.startsWith('+') && !line.startsWith('+++')) return ui.ok(line);\n return ui.dim(line);\n });\n}\n\nfunction buildGroupHeader(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'now'>>,\n): string {\n const errorCount = group.findings.filter(\n (f) => f.severity === 'error',\n ).length;\n const warnCount = group.findings.filter((f) => f.severity === 'warn').length;\n const infoCount = group.findings.filter((f) => f.severity === 'info').length;\n\n const worst = group.findings[0]?.severity ?? 'info';\n const color = severityColor(ui, worst);\n const glyph = severityGlyph(ui, worst);\n\n const parts: string[] = [];\n if (errorCount > 0)\n parts.push(`${errorCount} error${errorCount === 1 ? '' : 's'}`);\n if (warnCount > 0)\n parts.push(`${warnCount} warning${warnCount === 1 ? '' : 's'}`);\n if (infoCount > 0) parts.push(`${infoCount} info`);\n\n // The representative for the group's origin badge is the first (i.e.\n // worst-severity, then earliest) finding that carries one — a check\n // almost always shares one origin, and this avoids a badge per line.\n const withOrigin = group.findings.find((f) => f.origin !== undefined);\n const badge =\n withOrigin?.origin === undefined\n ? ''\n : ` ${ui.badge(\n withOrigin.createdAt\n ? `${withOrigin.origin} · ${relativeTime(withOrigin.createdAt, opts.now)}`\n : withOrigin.origin,\n originBadgeKind(withOrigin.origin),\n )}`;\n\n return `${color(`${glyph} ${group.check}`)} · ${parts.join(', ')}${badge}`;\n}\n\nfunction buildGroupBody(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'maxPerGroup' | 'cwd'>>,\n): string[] {\n const uniqueHints = [...new Set(group.findings.map((f) => f.hint))];\n const [firstHint] = uniqueHints;\n const dedupedHint =\n uniqueHints.length === 1 && firstHint !== undefined ? firstHint : null;\n\n const visible = group.findings.slice(0, opts.maxPerGroup);\n const overflow = group.findings.length - visible.length;\n\n const lines: string[] = [];\n let codeframeRendered = false;\n\n for (const finding of visible) {\n const location =\n finding.file !== undefined\n ? ui.accent(\n finding.line !== undefined\n ? `${finding.file}:${finding.line}`\n : finding.file,\n )\n : null;\n const message = renderMessage(ui, finding);\n\n lines.push(` ${ui.dim('│')}${location ? ` ${location}` : ''} ${message}`);\n\n if (finding.detail !== undefined) {\n lines.push(\n ...renderDetailLines(ui, finding.detail).map((line) => ` ${line}`),\n );\n }\n\n if (dedupedHint === null) {\n lines.push(` ${ui.hint(finding.hint)}`);\n }\n\n if (!codeframeRendered) {\n const frame = tryCodeframe(ui, finding, opts.cwd);\n if (frame !== null) {\n codeframeRendered = true;\n lines.push(\n frame\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n'),\n );\n }\n }\n }\n\n if (overflow > 0) {\n lines.push(\n ` ${ui.dim(`${ellipsis(ui)} and ${overflow} more in this group`)}`,\n );\n }\n\n if (dedupedHint !== null) {\n lines.push(` ${ui.hint(dedupedHint)}`);\n }\n\n return lines;\n}\n\n/**\n * Pure formatter — no I/O beyond an optional codeframe file read, no\n * printing. Kept separate from `renderFindings` so tests can assert exact\n * strings against a `Ui` built with a known color/glyph decision, without\n * constructing a full `CliContext`.\n */\nexport function formatFindings(\n ui: Ui,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): string {\n if (findings.length === 0) return '';\n\n const maxPerGroup = options.maxPerGroup ?? DEFAULT_MAX_PER_GROUP;\n const cwd = options.cwd ?? process.cwd();\n const now = options.now ?? new Date();\n\n const groupsByCheck = new Map<string, Group>();\n findings.forEach((finding, index) => {\n const existing = groupsByCheck.get(finding.check);\n if (existing) {\n existing.findings.push(finding);\n return;\n }\n groupsByCheck.set(finding.check, {\n check: finding.check,\n findings: [finding],\n order: index,\n });\n });\n\n const groups = [...groupsByCheck.values()];\n for (const group of groups) {\n group.findings.sort(\n (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity],\n );\n }\n groups.sort((a, b) => {\n const worstA = SEVERITY_RANK[a.findings[0]?.severity ?? 'info'];\n const worstB = SEVERITY_RANK[b.findings[0]?.severity ?? 'info'];\n return worstA - worstB || a.order - b.order;\n });\n\n const blocks = groups.map((group) => {\n const header = buildGroupHeader(ui, group, { now });\n const body = buildGroupBody(ui, group, { maxPerGroup, cwd });\n return [header, ...body].join('\\n');\n });\n\n const totalErrors = findings.filter((f) => f.severity === 'error').length;\n const totalWarnings = findings.filter((f) => f.severity === 'warn').length;\n const totalInfo = findings.filter((f) => f.severity === 'info').length;\n\n const summaryParts: string[] = [];\n if (totalErrors > 0)\n summaryParts.push(`${totalErrors} error${totalErrors === 1 ? '' : 's'}`);\n if (totalWarnings > 0)\n summaryParts.push(\n `${totalWarnings} warning${totalWarnings === 1 ? '' : 's'}`,\n );\n if (totalInfo > 0 && totalErrors === 0 && totalWarnings === 0) {\n summaryParts.push(`${totalInfo} info`);\n }\n\n const summaryColor =\n totalErrors > 0 ? ui.err : totalWarnings > 0 ? ui.warn : ui.dim;\n const summaryGlyph =\n totalErrors > 0\n ? ui.glyphs.err\n : totalWarnings > 0\n ? ui.glyphs.warn\n : ui.glyphs.info;\n const checksCount = groups.length;\n\n const summary = summaryColor(\n `${summaryGlyph} ${summaryParts.join(', ')} across ${checksCount} check${\n checksCount === 1 ? '' : 's'\n }`,\n );\n\n return [...blocks, summary].join('\\n\\n');\n}\n\n/** Render the shared findings block to `ctx`'s human-mode output (stderr). A no-op for an empty findings array. */\nexport function renderFindings(\n ctx: CliContext,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): void {\n const block = formatFindings(ctx.ui, findings, options);\n if (block !== '') ctx.log(block);\n}\n"]}
1
+ {"version":3,"file":"findings-renderer.js","sourceRoot":"","sources":["../src/findings-renderer.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 { CliContext } from './context';\nimport { FINDING_DOCS } from './docs-links';\nimport type { GateFinding } from './publish-api';\nimport { ENCLOSING_NOTE } from './source-locator';\nimport type { BadgeKind, Ui } from './ui';\n\n/**\n * The shared findings block: one rendering used by `validate` (a failed run),\n * `publish` (a gate refusal), and `status --findings`, so a partner sees the\n * exact same shape wherever a finding shows up. This is the ONLY findings\n * renderer in the package — nothing else may hand-format a `Finding`/\n * `GateFinding` array for a human.\n *\n * Deliberately structural rather than importing `Finding` or `GateFinding`\n * directly as the parameter type: both already satisfy this shape (their\n * extra/missing fields are all optional), and a structural type keeps this\n * module from caring which of the two ships that array.\n */\nexport interface RenderableFinding {\n check: string;\n severity: 'error' | 'warn' | 'info';\n file?: string;\n line?: number;\n message: string;\n hint: string;\n /** `'gate' | 'ci' | 'host' | 'revocation'` on the wire — see `GateFinding`. */\n origin?: string;\n createdAt?: string;\n resolvedAt?: string | null;\n /**\n * Optional multi-line supplement rendered directly under the finding's\n * message — currently only `ekanos test`'s curated failures appendix sets\n * this (an assertion diff). Additive: neither `Finding` nor `GateFinding`\n * carries it, so every pre-existing caller renders byte-identically to\n * before. Lines are shown as-is except a leading `-`/`+` (unified-diff\n * style), which is colored red/green respectively.\n */\n detail?: string;\n}\n\n// Compile-time proof the two real shapes this renders are both assignable.\nconst _typeCheck: (f: Finding | GateFinding) => RenderableFinding = (f) => f;\nvoid _typeCheck;\n\nexport interface RenderFindingsOptions {\n /** Findings shown per group before an overflow line takes over. Default 10. */\n maxPerGroup?: number;\n /** Base for resolving a finding's `file` before reading it for a codeframe. Default `process.cwd()`. */\n cwd?: string;\n /** Injectable for `relativeTime` snapshot tests. Default `new Date()`. */\n now?: Date;\n}\n\nconst DEFAULT_MAX_PER_GROUP = 10;\n\nconst SEVERITY_RANK: Record<RenderableFinding['severity'], number> = {\n error: 0,\n warn: 1,\n info: 2,\n};\n\ninterface Group {\n check: string;\n findings: RenderableFinding[];\n /** Insertion order, for a stable tie-break when sorting groups. */\n order: number;\n}\n\n/**\n * `relativeTime('2026-09-01T10:00:00Z')` → `\"2h ago\"`. Deliberately coarse\n * (rounds to the nearest unit, one unit at a time) — this is a findings-list\n * badge, not an audit-log timestamp, and a partner scanning for \"is this\n * recent\" doesn't need sub-minute precision.\n */\nexport function relativeTime(iso: string, now: Date = new Date()): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n\n const diffSec = Math.max(0, Math.round((now.getTime() - then) / 1000));\n\n if (diffSec < 5) return 'just now';\n if (diffSec < 60) return `${diffSec}s ago`;\n\n const diffMin = Math.round(diffSec / 60);\n if (diffMin < 60) return `${diffMin}m ago`;\n\n const diffHour = Math.round(diffMin / 60);\n if (diffHour < 24) return `${diffHour}h ago`;\n\n const diffDay = Math.round(diffHour / 24);\n if (diffDay < 30) return `${diffDay}d ago`;\n\n const diffMonth = Math.round(diffDay / 30);\n if (diffMonth < 12) return `${diffMonth}mo ago`;\n\n const diffYear = Math.round(diffMonth / 12);\n return `${diffYear}y ago`;\n}\n\n/**\n * A message beginning with a zod-style dot-path (`components.widgets[2].id: …`,\n * the shape `collectDefinitionFindings` produces — see the `Finding` doc\n * comment in `@ekanos/integration-schema`). Captures the path and the rest\n * separately so the renderer can dim the path instead of shouting the whole\n * sentence in one uniform color.\n */\nconst DOT_PATH_MESSAGE =\n /^([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+|\\[\\d+\\])*): ([^]+)$/;\n\n/**\n * Render one finding's message: a leading dot-path identical to the\n * finding's own `file` is pure duplication (the path would just repeat what\n * `file:line` already told the reader) and is dropped; any other dot-path\n * prefix is kept but dimmed, so the sentence itself reads at full contrast.\n * A message with no dot-path prefix passes through untouched.\n *\n * A trailing `source-locator.ts` `ENCLOSING_NOTE` (plain text in the data —\n * see that module's doc comment on why it never carries color itself) is\n * peeled off before the dot-path handling above and dimmed on its own, then\n * reattached — so it stays visually secondary to the message itself in\n * exactly the case where the frame it accompanies points at the enclosing\n * object rather than the missing key.\n */\nfunction renderMessage(ui: Ui, finding: RenderableFinding): string {\n const enclosingSuffix = ` ${ENCLOSING_NOTE}`;\n const hasEnclosingNote = finding.message.endsWith(enclosingSuffix);\n const message = hasEnclosingNote\n ? finding.message.slice(0, -enclosingSuffix.length)\n : finding.message;\n\n const match = DOT_PATH_MESSAGE.exec(message);\n const rendered = (() => {\n if (!match) return message;\n\n const [, dotPath, rest] = match;\n if (dotPath === finding.file) return rest ?? message;\n\n return `${ui.dim(dotPath ?? '')}: ${rest ?? ''}`;\n })();\n\n return hasEnclosingNote ? `${rendered} ${ui.dim(ENCLOSING_NOTE)}` : rendered;\n}\n\nfunction severityColor(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): (text: string) => string {\n if (severity === 'error') return ui.err;\n if (severity === 'warn') return ui.warn;\n return ui.dim;\n}\n\nfunction severityGlyph(\n ui: Ui,\n severity: RenderableFinding['severity'],\n): string {\n if (severity === 'warn') return ui.glyphs.warn;\n if (severity === 'info') return ui.glyphs.info;\n return ui.glyphs.err;\n}\n\nfunction ellipsis(ui: Ui): string {\n return ui.glyphs.ok === '✓' ? '…' : '...';\n}\n\n/** Attempts a codeframe for `finding`; `null` when it isn't renderable (no line, or the file can't be read locally). */\nfunction tryCodeframe(\n ui: Ui,\n finding: RenderableFinding,\n cwd: string,\n): string | null {\n if (finding.file === undefined || finding.line === undefined) return null;\n\n const resolved = path.resolve(cwd, finding.file);\n if (!fs.existsSync(resolved)) return null;\n\n try {\n const source = fs.readFileSync(resolved, 'utf8');\n return ui.codeframe({ file: finding.file, line: finding.line, source });\n } catch {\n // Best-effort decoration — an unreadable file (permissions, a race with\n // a concurrent edit) just means no codeframe, never a renderer failure.\n return null;\n }\n}\n\nfunction originBadgeKind(_origin: string): BadgeKind {\n return 'dim';\n}\n\n/**\n * Renders a finding's `detail` block, one line per source line: a\n * unified-diff `-` prefix is red, a `+` prefix is green, everything else\n * (context lines, \"Expected\"/\"Received\" headers, an overflow note) is dim.\n * `---`/`+++` file-header lines never appear here — `test-report.ts` never\n * emits them — but are guarded against anyway since this is the one place\n * that would mis-color them.\n */\nfunction renderDetailLines(ui: Ui, detail: string): string[] {\n return detail.split('\\n').map((line) => {\n if (line.startsWith('-') && !line.startsWith('---')) return ui.err(line);\n if (line.startsWith('+') && !line.startsWith('+++')) return ui.ok(line);\n return ui.dim(line);\n });\n}\n\nfunction buildGroupHeader(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'now'>>,\n): string {\n const errorCount = group.findings.filter(\n (f) => f.severity === 'error',\n ).length;\n const warnCount = group.findings.filter((f) => f.severity === 'warn').length;\n const infoCount = group.findings.filter((f) => f.severity === 'info').length;\n\n const worst = group.findings[0]?.severity ?? 'info';\n const color = severityColor(ui, worst);\n const glyph = severityGlyph(ui, worst);\n\n const parts: string[] = [];\n if (errorCount > 0)\n parts.push(`${errorCount} error${errorCount === 1 ? '' : 's'}`);\n if (warnCount > 0)\n parts.push(`${warnCount} warning${warnCount === 1 ? '' : 's'}`);\n if (infoCount > 0) parts.push(`${infoCount} info`);\n\n // The representative for the group's origin badge is the first (i.e.\n // worst-severity, then earliest) finding that carries one — a check\n // almost always shares one origin, and this avoids a badge per line.\n const withOrigin = group.findings.find((f) => f.origin !== undefined);\n const badge =\n withOrigin?.origin === undefined\n ? ''\n : ` ${ui.badge(\n withOrigin.createdAt\n ? `${withOrigin.origin} · ${relativeTime(withOrigin.createdAt, opts.now)}`\n : withOrigin.origin,\n originBadgeKind(withOrigin.origin),\n )}`;\n\n return `${color(`${glyph} ${group.check}`)} · ${parts.join(', ')}${badge}`;\n}\n\nfunction buildGroupBody(\n ui: Ui,\n group: Group,\n opts: Required<Pick<RenderFindingsOptions, 'maxPerGroup' | 'cwd'>>,\n): string[] {\n const uniqueHints = [...new Set(group.findings.map((f) => f.hint))];\n const [firstHint] = uniqueHints;\n const dedupedHint =\n uniqueHints.length === 1 && firstHint !== undefined ? firstHint : null;\n\n const visible = group.findings.slice(0, opts.maxPerGroup);\n const overflow = group.findings.length - visible.length;\n\n const lines: string[] = [];\n let codeframeRendered = false;\n\n for (const finding of visible) {\n const location =\n finding.file !== undefined\n ? ui.accent(\n finding.line !== undefined\n ? `${finding.file}:${finding.line}`\n : finding.file,\n )\n : null;\n const message = renderMessage(ui, finding);\n\n lines.push(` ${ui.dim('│')}${location ? ` ${location}` : ''} ${message}`);\n\n if (finding.detail !== undefined) {\n lines.push(\n ...renderDetailLines(ui, finding.detail).map((line) => ` ${line}`),\n );\n }\n\n if (dedupedHint === null) {\n lines.push(` ${ui.hint(finding.hint)}`);\n }\n\n if (!codeframeRendered) {\n const frame = tryCodeframe(ui, finding, opts.cwd);\n if (frame !== null) {\n codeframeRendered = true;\n lines.push(\n frame\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n'),\n );\n }\n }\n }\n\n if (overflow > 0) {\n lines.push(\n ` ${ui.dim(`${ellipsis(ui)} and ${overflow} more in this group`)}`,\n );\n }\n\n if (dedupedHint !== null) {\n lines.push(` ${ui.hint(dedupedHint)}`);\n }\n\n const docs = FINDING_DOCS[group.check];\n if (docs !== undefined) {\n lines.push(` ${ui.dim(`Docs: ${docs}`)}`);\n }\n\n return lines;\n}\n\n/**\n * Pure formatter — no I/O beyond an optional codeframe file read, no\n * printing. Kept separate from `renderFindings` so tests can assert exact\n * strings against a `Ui` built with a known color/glyph decision, without\n * constructing a full `CliContext`.\n */\nexport function formatFindings(\n ui: Ui,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): string {\n if (findings.length === 0) return '';\n\n const maxPerGroup = options.maxPerGroup ?? DEFAULT_MAX_PER_GROUP;\n const cwd = options.cwd ?? process.cwd();\n const now = options.now ?? new Date();\n\n const groupsByCheck = new Map<string, Group>();\n findings.forEach((finding, index) => {\n const existing = groupsByCheck.get(finding.check);\n if (existing) {\n existing.findings.push(finding);\n return;\n }\n groupsByCheck.set(finding.check, {\n check: finding.check,\n findings: [finding],\n order: index,\n });\n });\n\n const groups = [...groupsByCheck.values()];\n for (const group of groups) {\n group.findings.sort(\n (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity],\n );\n }\n groups.sort((a, b) => {\n const worstA = SEVERITY_RANK[a.findings[0]?.severity ?? 'info'];\n const worstB = SEVERITY_RANK[b.findings[0]?.severity ?? 'info'];\n return worstA - worstB || a.order - b.order;\n });\n\n const blocks = groups.map((group) => {\n const header = buildGroupHeader(ui, group, { now });\n const body = buildGroupBody(ui, group, { maxPerGroup, cwd });\n return [header, ...body].join('\\n');\n });\n\n const totalErrors = findings.filter((f) => f.severity === 'error').length;\n const totalWarnings = findings.filter((f) => f.severity === 'warn').length;\n const totalInfo = findings.filter((f) => f.severity === 'info').length;\n\n const summaryParts: string[] = [];\n if (totalErrors > 0)\n summaryParts.push(`${totalErrors} error${totalErrors === 1 ? '' : 's'}`);\n if (totalWarnings > 0)\n summaryParts.push(\n `${totalWarnings} warning${totalWarnings === 1 ? '' : 's'}`,\n );\n if (totalInfo > 0 && totalErrors === 0 && totalWarnings === 0) {\n summaryParts.push(`${totalInfo} info`);\n }\n\n const summaryColor =\n totalErrors > 0 ? ui.err : totalWarnings > 0 ? ui.warn : ui.dim;\n const summaryGlyph =\n totalErrors > 0\n ? ui.glyphs.err\n : totalWarnings > 0\n ? ui.glyphs.warn\n : ui.glyphs.info;\n const checksCount = groups.length;\n\n const summary = summaryColor(\n `${summaryGlyph} ${summaryParts.join(', ')} across ${checksCount} check${\n checksCount === 1 ? '' : 's'\n }`,\n );\n\n return [...blocks, summary].join('\\n\\n');\n}\n\n/** Render the shared findings block to `ctx`'s human-mode output (stderr). A no-op for an empty findings array. */\nexport function renderFindings(\n ctx: CliContext,\n findings: readonly RenderableFinding[],\n options: RenderFindingsOptions = {},\n): void {\n const block = formatFindings(ctx.ui, findings, options);\n if (block !== '') ctx.log(block);\n}\n"]}
@@ -10,6 +10,8 @@ export declare const GITIGNORE_ENTRY = ".ekanos/";
10
10
  * shell — a version bump means the shell may be wired against an older export
11
11
  * surface, so it is regenerated.
12
12
  */
13
+ /** Next's dev/build cache inside the generated shell. */
14
+ export declare const BUILD_CACHE_DIR = ".next";
13
15
  export declare const VERSION_MARKER = ".ekanos-version";
14
16
  /** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */
15
17
  export declare const HARNESS_CONFIG_FILENAME = "harness.config.ts";
@@ -147,6 +149,8 @@ export interface ScaffoldShellResult extends WrittenFiles {
147
149
  previousMarker: ShellMarker | null;
148
150
  /** True when anything that generated the shell has since changed. */
149
151
  stale: boolean;
152
+ /** `.next` under the shell was deleted because the shell was stale. */
153
+ buildCacheCleared: boolean;
150
154
  /**
151
155
  * Which of harness / cli / templates moved. Empty on a first run; this is
152
156
  * what lets `dev` say WHY it regenerated rather than just that it did.
@@ -16,6 +16,8 @@ export const GITIGNORE_ENTRY = '.ekanos/';
16
16
  * shell — a version bump means the shell may be wired against an older export
17
17
  * surface, so it is regenerated.
18
18
  */
19
+ /** Next's dev/build cache inside the generated shell. */
20
+ export const BUILD_CACHE_DIR = '.next';
19
21
  export const VERSION_MARKER = '.ekanos-version';
20
22
  /** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */
21
23
  export const HARNESS_CONFIG_FILENAME = 'harness.config.ts';
@@ -49,6 +51,10 @@ export const SHELL_FILES = [
49
51
  template: 'harness/app/[slug]/layout.tsx.tmpl',
50
52
  dest: 'app/[slug]/layout.tsx',
51
53
  },
54
+ {
55
+ template: 'harness/app/[slug]/page.tsx.tmpl',
56
+ dest: 'app/[slug]/page.tsx',
57
+ },
52
58
  {
53
59
  template: 'harness/app/[slug]/widgets/page.tsx.tmpl',
54
60
  dest: 'app/[slug]/widgets/page.tsx',
@@ -247,6 +253,16 @@ export function scaffoldShell(args) {
247
253
  }
248
254
  }
249
255
  const stale = staleReasons.length > 0;
256
+ // Next's persistent dev cache was built from the OLD shell and the OLD
257
+ // toolchain. Keeping it across a regeneration served a corrupted Tailwind
258
+ // candidate on the 0.1 → 0.4 jump (every page a build-error overlay until
259
+ // someone deleted the directory by hand). The shell knows it is stale; the
260
+ // cache goes with it.
261
+ const buildCacheDir = path.join(harnessDir, BUILD_CACHE_DIR);
262
+ const buildCacheCleared = stale && fs.existsSync(buildCacheDir);
263
+ if (buildCacheCleared) {
264
+ fs.rmSync(buildCacheDir, { recursive: true, force: true });
265
+ }
250
266
  const files = { created: [], updated: [], unchanged: [] };
251
267
  const vars = {
252
268
  SLUG: args.slug,
@@ -260,7 +276,8 @@ export function scaffoldShell(args) {
260
276
  previousVersion,
261
277
  previousMarker,
262
278
  stale,
263
- staleReasons });
279
+ staleReasons,
280
+ buildCacheCleared });
264
281
  }
265
282
  /**
266
283
  * Turn validated `sourceGlobs` into the `@source` lines the generated
@@ -1 +1 @@
1
- {"version":3,"file":"harness-scaffold.js","sourceRoot":"","sources":["../src/harness-scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AAE3D,yEAAyE;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAEhD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkD;IACxE,EAAE,QAAQ,EAAE,6BAA6B,EAAE,IAAI,EAAE,gBAAgB,EAAE;IACnE;QACE,QAAQ,EAAE,oCAAoC;QAC9C,IAAI,EAAE,uBAAuB;KAC9B;IACD,EAAE,QAAQ,EAAE,2BAA2B,EAAE,IAAI,EAAE,cAAc,EAAE;IAC/D;QACE,QAAQ,EAAE,oCAAoC;QAC9C,IAAI,EAAE,uBAAuB;KAC9B;IACD;QACE,QAAQ,EAAE,0CAA0C;QACpD,IAAI,EAAE,6BAA6B;KACpC;IACD;QACE,QAAQ,EAAE,qDAAqD;QAC/D,IAAI,EAAE,wCAAwC;KAC/C;IACD;QACE,QAAQ,EAAE,uCAAuC;QACjD,IAAI,EAAE,0BAA0B;KACjC;IACD;QACE,QAAQ,EAAE,6CAA6C;QACvD,IAAI,EAAE,gCAAgC;KACvC;IACD;QACE,QAAQ,EAAE,2CAA2C;QACrD,IAAI,EAAE,8BAA8B;KACrC;IACD;QACE,QAAQ,EAAE,6DAA6D;QACvE,IAAI,EAAE,gDAAgD;KACvD;IACD,EAAE,QAAQ,EAAE,8BAA8B,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACrE,EAAE,QAAQ,EAAE,iCAAiC,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC3E,EAAE,QAAQ,EAAE,4BAA4B,EAAE,IAAI,EAAE,eAAe,EAAE;IACjE,EAAE,QAAQ,EAAE,iCAAiC,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC3E,EAAE,QAAQ,EAAE,2BAA2B,EAAE,IAAI,EAAE,cAAc,EAAE;IAC/D;QACE,QAAQ,EAAE,uCAAuC;QACjD,IAAI,EAAE,0BAA0B;KACjC;CACF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,eAAe;IACf,MAAM;IACN,OAAO;IACP,WAAW;IACX,aAAa;IACb,sBAAsB;CACd,CAAC;AAgCX,MAAM,UAAU,4BAA4B,CAC1C,UAAkB,EAClB,WAAmB;IAEnB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAC5B,UAAU,EACV,cAAc,EACd,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EACzB,cAAc,CACf,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,MAAM,OAAO,GACX,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAC3C,CAAC,CAAE,MAAgC,CAAC,OAAO;YAC3C,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,iBAAiB,CACrB,GAAG,WAAW,kDACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,EACF,6BAA6B,UAAU,OAAO,WAAW,YAAY,CACtE,CAAC;IACJ,CAAC;AACH,CAAC;AAKD,MAAM,UAAU,oBAAoB,CAClC,UAAkB;IAElB,OAAO,4BAA4B,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;AACnE,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,2BAA2B,CAAC,UAAkB;;;IAC5D,aAAO,MAAA,oBAAoB,CAAC,UAAU,CAAC,0CAAE,OAAO,mCAAI,IAAI,CAAC;AAC3D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,qBAAqB,CAAC,MAAM,CACjC,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,EAAE,CAAC,UAAU,CACZ,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,CAC5D,CACJ,CAAC;AACJ,CAAC;AAqCD,MAAM,UAAU,oBAAoB,CAAC,UAAkB;;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACrD,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAEhE,MAAM,GAAG,GAAG,OAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,mCAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAEjD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAChF,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,6EAA6E;IAC7E,wEAAwE;IACxE,kEAAkE;IAClE,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,6DAA6D;IAC7D,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;YAC1B,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;YAC9C,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC7C,OAAO;YACL,OAAO;YACP,GAAG,EAAE,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;YACvD,SAAS,EAAE,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;SAC1E,CAAC;IACJ,CAAC;eAAO,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,KAAK,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,UAAkB;;;IACjD,aAAO,MAAA,eAAe,CAAC,UAAU,CAAC,0CAAE,OAAO,mCAAI,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CAAC,MAAmB;IAC1C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,QAAgB,EAChB,QAAgB,EAChB,IAAkB,EAClB,KAAa,EACb,KAAc;IAEd,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,uBAAuB;IACvB,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC;IAElC,IACE,CAAC,KAAK;QACN,IAAI,KAAK,MAAM;QACf,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,QAAQ,EAC7C,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IAED,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACrD,CAAC;AA2BD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAuB;;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3D,oBAAoB,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAE7C,MAAM,WAAW,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,MAAM,eAAe,SAAG,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,OAAO,mCAAI,IAAI,CAAC;IACxD,MAAM,MAAM,GAAgB;QAC1B,OAAO,EAAE,IAAI,CAAC,cAAc;QAC5B,GAAG,EAAE,UAAU,EAAE;QACjB,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;KACpE,CAAC;IAEF,uEAAuE;IACvE,4EAA4E;IAC5E,8DAA8D;IAC9D,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACnC,YAAY,CAAC,IAAI,CACf,GAAG,cAAc,sDAAsD;YACrE,WAAW,CACd,CAAC;IACJ,CAAC;IACD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,cAAc,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9C,YAAY,CAAC,IAAI,CACf,mBAAmB,cAAc,CAAC,OAAO,MAAM,MAAM,CAAC,OAAO,EAAE,CAChE,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CACf,eAAe,MAAA,cAAc,CAAC,GAAG,mCAAI,SAAS,MAAM,MAAM,CAAC,GAAG,EAAE,CACjE,CAAC;QACJ,CAAC;QACD,IAAI,cAAc,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;YAClD,YAAY,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;IAEtC,MAAM,KAAK,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG;QACX,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,YAAY,EAAE,iBAAiB,OAAC,IAAI,CAAC,WAAW,mCAAI,EAAE,CAAC;KACxD,CAAC;IAEF,KAAK,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7C,cAAc,CACZ,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,EAC3B,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,EAC5B,KAAK,EACL,IAAI,EACJ,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;IAED,cAAc,CACZ,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACrC,eAAe,CAAC,MAAM,CAAC,EACvB,KAAK,EACL,cAAc,EACd,IAAI,CAAC,KAAK,CACX,CAAC;IAEF,uCACK,KAAK,KACR,UAAU;QACV,eAAe;QACf,cAAc;QACd,KAAK;QACL,YAAY,IACZ;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CAAC,KAAwB;IACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,gBAAgB,GAAG,IAAI,IAAI,CAAC,CAAC;IAC3E,OAAO,+CAA+C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAC7E,CAAC;AAED,kEAAkE;AAClE,MAAM,gBAAgB,GAAG,WAAW,CAAC;AAErC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,KAAc;IAEd,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAEtC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,UAAU,2DAA2D,EACxE,UAAU,UAAU,qDAAqD;YACvE,qBAAqB,CACxB,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QAC/C,MAAM,iBAAiB,CACrB,GAAG,UAAU,iCAAiC,EAC9C,kBAAkB,UAAU,6BAA6B,CAC1D,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,KAAK,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAE/C,yEAAyE;IACzE,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;IACrD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAEnD,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,iBAAiB,CACrB,GAAG,UAAU,kCAAkC,cAAc,WAAW;QACtE,0CAA0C,EAC5C,UAAU,UAAU,6CAA6C;QAC/D,mEAAmE;QACnE,gCAAgC,CACnC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,UAAU,CAAC,OAAO,CAAC,oCAAoC,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE9C,4CAA4C;IAC5C,IAAI,0BAA0B,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,MAAM,iBAAiB,CACrB,uEAAuE;YACrE,wBAAwB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,EACtD,oEAAoE;YAClE,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC,CAAC;AAC1C,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,YAAwD;IAExD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAEtC,2EAA2E;IAC3E,6EAA6E;IAC7E,0EAA0E;IAC1E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,UAAU,2DAA2D,EACxE,WAAW,UAAU,8CAA8C;YACjE,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC;IAE7E,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAC1B,YAAwD;IAExD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,iBAAiB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CACV,2BAA2B,KAAK,WAAW,kBAAkB,CAC3D,WAAW,CAAC,KAAK,CAClB,GAAG,CACL,CAAC;QACF,OAAO,CAAC,IAAI,CACV,YAAY,CAAC,8BAA8B,EAAE;YAC3C,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,WAAW,CAAC,IAAI;SACvB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,YAAY,CAAC,wBAAwB,EAAE;QAC5C,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QACvC,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;KACtC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAC7D,IAAI,CAAC,WAAW,EAAE,CACnB,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;;IACrD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,2EAA2E,eAAe,IAAI,CAAC;IAE7G,MAAM,IAAI,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;IAEzC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,iBAAiB,CACrB,GAAG,aAAa,uDAAuD,EACvE,QAAQ,eAAe,iDAAiD;YACtE,GAAG,aAAa,iDAAiD,CACpE,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,SAAG,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,mCAAI,EAAE,CAAC;IACnE,IAAI,eAAe,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5C,MAAM,SAAS,GACb,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,kBAAkB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,MAAc;IACrC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;IAC9E,OAAO,MAAM;SACV,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3C,CAAC","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { cliVersion, templatesFingerprint } from './compatibility';\nimport { invalidStateError, preconditionError } from './errors';\nimport {\n appendFileNoFollow,\n classifyPath,\n readFileNoFollow,\n writeFileNoFollow,\n} from './safe-fs';\nimport { readTemplate } from './templates';\n\n/** The dependency that owns every line of harness behaviour. */\nexport const HARNESS_PACKAGE = '@ekanos/harness';\n\n/** Where the generated shell lives, relative to the project root. */\nexport const HARNESS_DIR = path.join('.ekanos', 'harness');\n\n/** The single `.gitignore` line that covers the whole generated tree. */\nexport const GITIGNORE_ENTRY = '.ekanos/';\n\n/**\n * The staleness marker, inside the generated tree so deleting the tree also\n * deletes the marker. Holds the `@ekanos/harness` version that generated the\n * shell — a version bump means the shell may be wired against an older export\n * surface, so it is regenerated.\n */\nexport const VERSION_MARKER = '.ekanos-version';\n\n/** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */\nexport const HARNESS_CONFIG_FILENAME = 'harness.config.ts';\n\n/**\n * Every file in the generated shell: bundled template → path inside\n * `.ekanos/harness/`. The ten route files are mechanically derivable from the\n * export map alone — which is the test of whether the package boundary is in\n * the right place. If a shell file ever needs a judgment call, the boundary\n * has moved, not the template.\n *\n * `app/api/integrations/[slug]/[...path]/route.ts` is the shell's one SERVER\n * route: it injects the registry into `@ekanos/harness/api`, which serves the\n * live-mode OAuth loop, storage bridge and trigger route over a server-held\n * context. It is the third registry injection point, beside the two pages.\n *\n * `app/harness-shell.tsx` exists because a `HarnessIntegration` holds React\n * component references and MCP `run` functions. Next cannot serialize those\n * across a server→client boundary, so the `'use client'` mark has to sit ABOVE\n * the point where the registry is injected — hence a client shell of its own\n * rather than a client `RootLayout` called from a server layout. Keeping\n * `app/page.tsx` a server component is what lets `/` prerender static.\n */\nexport const SHELL_FILES: readonly { template: string; dest: string }[] = [\n { template: 'harness/app/layout.tsx.tmpl', dest: 'app/layout.tsx' },\n {\n template: 'harness/app/harness-shell.tsx.tmpl',\n dest: 'app/harness-shell.tsx',\n },\n { template: 'harness/app/page.tsx.tmpl', dest: 'app/page.tsx' },\n {\n template: 'harness/app/[slug]/layout.tsx.tmpl',\n dest: 'app/[slug]/layout.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/page.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/[widgetId]/page.tsx',\n },\n {\n template: 'harness/app/[slug]/tile/page.tsx.tmpl',\n dest: 'app/[slug]/tile/page.tsx',\n },\n {\n template: 'harness/app/[slug]/activation/page.tsx.tmpl',\n dest: 'app/[slug]/activation/page.tsx',\n },\n {\n template: 'harness/app/[slug]/triggers/page.tsx.tmpl',\n dest: 'app/[slug]/triggers/page.tsx',\n },\n {\n template: 'harness/app/api/integrations/[slug]/[...path]/route.ts.tmpl',\n dest: 'app/api/integrations/[slug]/[...path]/route.ts',\n },\n { template: 'harness/next.config.mjs.tmpl', dest: 'next.config.mjs' },\n { template: 'harness/postcss.config.mjs.tmpl', dest: 'postcss.config.mjs' },\n { template: 'harness/tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'harness/styles/globals.css.tmpl', dest: 'styles/globals.css' },\n { template: 'harness/package.json.tmpl', dest: 'package.json' },\n {\n template: 'harness/types/team-accounts.d.ts.tmpl',\n dest: 'types/team-accounts.d.ts',\n },\n];\n\n/**\n * Packages the shell needs to resolve upward out of the partner's own\n * `node_modules`. The shell declares no dependencies of its own by design, so\n * a missing one shows up as an opaque Next resolution error at dev-server\n * start; checking here turns that into one precondition with one fix.\n */\nexport const REQUIRED_DEPENDENCIES = [\n HARNESS_PACKAGE,\n 'next',\n 'react',\n 'react-dom',\n 'tailwindcss',\n '@tailwindcss/postcss',\n] as const;\n\nexport interface WrittenFiles {\n created: string[];\n updated: string[];\n unchanged: string[];\n}\n\n/**\n * Read an installed package's `package.json` out of a project's TOP-LEVEL\n * `node_modules`. Resolved by direct path join rather than `require.resolve`\n * on purpose, for two independent reasons:\n *\n * - The CLI runs from its own install and must read the version of the copy\n * in THE CALLER'S project, not one hoisted next to itself.\n * - Several of the packages this reads (`@ekanos/integration-schema`\n * included) declare an `exports` map with no `./package.json` subpath, so\n * `require.resolve('<pkg>/package.json')` throws\n * `ERR_PACKAGE_PATH_NOT_EXPORTED` under Node's ESM resolver. A plain file\n * read is not subject to the export map at all.\n *\n * Shared by every \"what does this project have installed?\" check in the\n * CLI — `readInstalledHarness` below, the schema-skew gate, and `upgrade`'s\n * current-toolchain report — so there is exactly one implementation of \"read\n * a package's on-disk version\", not a subtly different one per call site.\n */\nexport interface InstalledPackage {\n version: string;\n /** The raw package.json, for callers that need more than the version. */\n manifest: unknown;\n}\n\nexport function readInstalledPackageManifest(\n projectDir: string,\n packageName: string,\n): InstalledPackage | null {\n const manifestPath = path.join(\n projectDir,\n 'node_modules',\n ...packageName.split('/'),\n 'package.json',\n );\n if (!fs.existsSync(manifestPath)) return null;\n\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n if (typeof version !== 'string' || version.length === 0) return null;\n return { version, manifest: parsed };\n } catch (error) {\n throw preconditionError(\n `${packageName}'s package.json in node_modules is unreadable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Reinstall dependencies in ${projectDir} so ${packageName} resolves.`,\n );\n }\n}\n\n/** Read the installed `@ekanos/harness` version out of the partner's project. */\nexport type InstalledHarness = InstalledPackage;\n\nexport function readInstalledHarness(\n projectDir: string,\n): InstalledHarness | null {\n return readInstalledPackageManifest(projectDir, HARNESS_PACKAGE);\n}\n\n/** Just the installed version, for callers that need nothing else. */\nexport function readInstalledHarnessVersion(projectDir: string): string | null {\n return readInstalledHarness(projectDir)?.version ?? null;\n}\n\n/** Which of `REQUIRED_DEPENDENCIES` are not installed in the project root. */\nexport function missingDependencies(projectDir: string): string[] {\n return REQUIRED_DEPENDENCIES.filter(\n (name) =>\n !fs.existsSync(\n path.join(projectDir, 'node_modules', name, 'package.json'),\n ),\n );\n}\n\n/**\n * What generated an existing shell.\n *\n * All three fields matter, and recording only `harness` was a real hole: the\n * templates ship with the CLI, so a shell can be stale because the CLI changed\n * while the harness did not. `templates` is the authoritative one — it is a\n * hash of the template sources, so it moves whenever what we generate moves,\n * including for a CLI build that never shipped a version bump.\n */\nexport interface ShellMarker {\n harness: string;\n /** null for a legacy bare-version marker written before this was recorded. */\n cli: string | null;\n templates: string | null;\n}\n\n/**\n * Three outcomes, not two, because \"no marker\" and \"unreadable marker\" call\n * for opposite responses.\n *\n * A marker's PRESENCE is itself evidence the tree is ours — a squatter does\n * not write `.ekanos-version` — and the likeliest cause of an unreadable one\n * is our own interrupted write. Everything under `.ekanos/harness/` is\n * CLI-owned, gitignored and reproducible from the templates, so regenerating\n * destroys nothing: there is no value at risk to justify charging a partner a\n * confusing `--force` to recover from our own bad write.\n *\n * An ABSENT marker over a non-empty tree is the opposite: nothing says the\n * files are ours, so we refuse. That is the L1 rule and it stays.\n */\nexport type MarkerState =\n | { kind: 'missing' }\n | { kind: 'corrupt' }\n | { kind: 'ok'; marker: ShellMarker };\n\nexport function readShellMarkerState(harnessDir: string): MarkerState {\n const marker = path.join(harnessDir, VERSION_MARKER);\n if (classifyPath(marker) !== 'file') return { kind: 'missing' };\n\n const raw = (readFileNoFollow(harnessDir, marker) ?? '').trim();\n if (raw.length === 0) return { kind: 'corrupt' };\n\n const parsed = parseMarker(raw);\n return parsed === null ? { kind: 'corrupt' } : { kind: 'ok', marker: parsed };\n}\n\nfunction parseMarker(raw: string): ShellMarker | null {\n // v1 markers were a bare version string. Read them rather than crashing, and\n // report the unknown fields as null so the shell counts as stale and is\n // regenerated once — which is the correct outcome for an upgrade.\n //\n // It must actually LOOK like a version: without this, any garbage that does\n // not happen to start with `{` — a truncated write, a stray word — reads as\n // a legacy marker named after the garbage, and the corrupt-marker path never\n // fires. That is how a truncated `.ekanos-version` was silently reported as\n // \"@ekanos/harness truncat → 0.1.0\" instead of \"unreadable\".\n if (!raw.startsWith('{')) {\n return /^\\d+\\.\\d+/.test(raw)\n ? { harness: raw, cli: null, templates: null }\n : null;\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const harness = record.harness;\n if (typeof harness !== 'string') return null;\n return {\n harness,\n cli: typeof record.cli === 'string' ? record.cli : null,\n templates: typeof record.templates === 'string' ? record.templates : null,\n };\n } catch {\n return null;\n }\n}\n\n/** The parsed marker, or null when it is absent OR unreadable. */\nexport function readShellMarker(harnessDir: string): ShellMarker | null {\n const state = readShellMarkerState(harnessDir);\n return state.kind === 'ok' ? state.marker : null;\n}\n\n/** The `@ekanos/harness` version an existing shell was generated against. */\nexport function readShellVersion(harnessDir: string): string | null {\n return readShellMarker(harnessDir)?.harness ?? null;\n}\n\nfunction serializeMarker(marker: ShellMarker): string {\n return `${JSON.stringify(marker, null, 2)}\\n`;\n}\n\n/**\n * Write a generated file, but ONLY when its bytes would change.\n *\n * This is what makes a re-run cheap and a watcher quiet: an unchanged file is\n * not rewritten, so its mtime does not move and Next's dev server does not\n * rebuild. It also means a hand-edit inside the CLI-owned tree is silently\n * corrected on the next run, which is the intended contract for `.ekanos/`.\n */\nexport function writeIfChanged(\n root: string,\n destPath: string,\n contents: string,\n into: WrittenFiles,\n label: string,\n force: boolean,\n): void {\n // `classifyPath` uses lstat, so a symlink here is reported as a symlink\n // rather than as whatever it points at — including a DANGLING one, which\n // `existsSync` would call absent and a plain write would then materialize\n // outside the project.\n const kind = classifyPath(destPath);\n const exists = kind !== 'missing';\n\n if (\n !force &&\n kind === 'file' &&\n readFileNoFollow(root, destPath) === contents\n ) {\n into.unchanged.push(label);\n return;\n }\n\n writeFileNoFollow(root, destPath, contents);\n (exists ? into.updated : into.created).push(label);\n}\n\nexport interface ScaffoldShellArgs {\n projectDir: string;\n slug: string;\n harnessVersion: string;\n /** Rewrite every file even when its bytes already match. */\n force: boolean;\n /** Extra Tailwind @source globs from ekanos.json, already validated. */\n sourceGlobs?: readonly string[];\n}\n\nexport interface ScaffoldShellResult extends WrittenFiles {\n harnessDir: string;\n /** The version the shell carried before this run — null on a first run. */\n previousVersion: string | null;\n /** What generated the previous shell, or null on a first run. */\n previousMarker: ShellMarker | null;\n /** True when anything that generated the shell has since changed. */\n stale: boolean;\n /**\n * Which of harness / cli / templates moved. Empty on a first run; this is\n * what lets `dev` say WHY it regenerated rather than just that it did.\n */\n staleReasons: string[];\n}\n\n/**\n * Generate `.ekanos/harness/**` for `harnessVersion`. Every file here is\n * CLI-owned and gitignored, so regeneration needs no permission and asks no\n * question — the partner owns `harness.config.ts` and their integration\n * source, and nothing else.\n */\nexport function scaffoldShell(args: ScaffoldShellArgs): ScaffoldShellResult {\n const harnessDir = path.join(args.projectDir, HARNESS_DIR);\n assertShellDirIsOurs(harnessDir, args.force);\n\n const markerState = readShellMarkerState(harnessDir);\n const previousMarker = markerState.kind === 'ok' ? markerState.marker : null;\n const previousVersion = previousMarker?.harness ?? null;\n const marker: ShellMarker = {\n harness: args.harnessVersion,\n cli: cliVersion(),\n templates: templatesFingerprint(SHELL_FILES.map((f) => f.template)),\n };\n\n // A shell is current only if EVERYTHING that produced it is unchanged.\n // Checking the harness version alone certified shells generated by an older\n // CLI, against templates that had since moved, as up to date.\n const staleReasons: string[] = [];\n if (markerState.kind === 'corrupt') {\n staleReasons.push(\n `${VERSION_MARKER} was unreadable, so the existing shell could not be ` +\n 'certified',\n );\n }\n if (previousMarker !== null) {\n if (previousMarker.harness !== marker.harness) {\n staleReasons.push(\n `@ekanos/harness ${previousMarker.harness} → ${marker.harness}`,\n );\n }\n if (previousMarker.cli !== marker.cli) {\n staleReasons.push(\n `@ekanos/cli ${previousMarker.cli ?? 'unknown'} → ${marker.cli}`,\n );\n }\n if (previousMarker.templates !== marker.templates) {\n staleReasons.push('the shell templates changed');\n }\n }\n const stale = staleReasons.length > 0;\n\n const files: WrittenFiles = { created: [], updated: [], unchanged: [] };\n const vars = {\n SLUG: args.slug,\n SOURCE_GLOBS: renderSourceGlobs(args.sourceGlobs ?? []),\n };\n\n for (const { template, dest } of SHELL_FILES) {\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, dest),\n readTemplate(template, vars),\n files,\n dest,\n args.force,\n );\n }\n\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, VERSION_MARKER),\n serializeMarker(marker),\n files,\n VERSION_MARKER,\n args.force,\n );\n\n return {\n ...files,\n harnessDir,\n previousVersion,\n previousMarker,\n stale,\n staleReasons,\n };\n}\n\n/**\n * Turn validated `sourceGlobs` into the `@source` lines the generated\n * stylesheet carries, each relative to the shell's `styles/` directory.\n *\n * Tailwind never scans what it is not pointed at, and a glob that matches\n * nothing fails silently — so an integration living outside the default\n * layout renders completely unstyled with no error anywhere. That is what\n * `sourceGlobs` is for, and until now it was parsed and thrown away.\n */\nfunction renderSourceGlobs(globs: readonly string[]): string {\n if (globs.length === 0) return '';\n const lines = globs.map((glob) => `@source '${SHELL_TO_PROJECT}${glob}';`);\n return `\\n/* From \"sourceGlobs\" in ekanos.json. */\\n${lines.join('\\n')}\\n`;\n}\n\n/** From `.ekanos/harness/styles/` back up to the project root. */\nconst SHELL_TO_PROJECT = '../../../';\n\n/**\n * Establish that `.ekanos/harness` is a tree WE generated before writing a\n * single byte into it, and return the version marker it carried.\n *\n * Path location alone is not ownership. A directory that exists but carries no\n * `.ekanos-version` was put there by something else — a partner, another tool,\n * a symlink — and clobbering it because it happens to sit at the path we like\n * is the same class of mistake as following a symlink. An empty directory is\n * fine (that is just `mkdir -p` having run), and `--force` is the explicit\n * override.\n *\n * Runs BEFORE `bootstrapHarnessConfig` in `dev`, so a shell that cannot be\n * written leaves the project completely untouched rather than half-scaffolded.\n */\nexport function assertShellDirIsOurs(\n harnessDir: string,\n force: boolean,\n): string | null {\n const kind = classifyPath(harnessDir);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${harnessDir} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Delete ${harnessDir} and re-run \"ekanos dev\" — the shell is generated, ` +\n 'so nothing is lost.',\n );\n }\n\n if (kind !== 'missing' && kind !== 'directory') {\n throw invalidStateError(\n `${harnessDir} exists and is not a directory.`,\n `Move or delete ${harnessDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') return null;\n\n const state = readShellMarkerState(harnessDir);\n\n // A marker we wrote, however mangled, still identifies the tree as ours.\n if (state.kind === 'ok') return state.marker.harness;\n if (state.kind === 'corrupt' || force) return null;\n\n if (fs.readdirSync(harnessDir).length === 0) return null;\n\n throw invalidStateError(\n `${harnessDir} already contains files but no ${VERSION_MARKER} marker, ` +\n 'so it was not generated by \"ekanos dev\".',\n `Delete ${harnessDir} if you do not need it, or pass --force to ` +\n 'overwrite it. Everything \"ekanos dev\" generates is reproducible; ' +\n 'anything else in there is not.',\n );\n}\n\n/**\n * The module specifier `harness.config.ts` uses to import the partner's\n * integration: the `entry` from ekanos.json, made relative and extension-free.\n * \"src/integration.ts\" → \"src/integration\".\n */\nexport function entryImportSpecifier(entry: string): string {\n const normalized = entry.split(path.sep).join('/').replace(/^\\.\\//, '');\n return normalized.replace(/\\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, '');\n}\n\n/**\n * The same specifier as a JavaScript string LITERAL, ready to drop into a\n * generated `import` statement.\n *\n * `entry` is attacker-influenced data from `ekanos.json`, and a POSIX filename\n * may legally contain a quote or a newline. Interpolating it raw would let a\n * crafted-but-valid path close the string and append statements to the module\n * we generate. `JSON.stringify` produces a correctly escaped literal, which is\n * the actual fix; the explicit rejection below is a second line that also\n * gives the partner a comprehensible error instead of a bizarre filename\n * silently appearing, escaped, in their config.\n *\n * This does not cross a trust boundary on its own — a partner's own code runs\n * unsandboxed anyway — but \"the templates cannot be injected into\" is a claim\n * we make, and it should be true.\n */\nexport function entryImportLiteral(entry: string): string {\n const specifier = entryImportSpecifier(entry);\n\n // eslint-disable-next-line no-control-regex\n if (/[\"'`\\\\\\n\\r\\u0000-\\u001f]/.test(specifier)) {\n throw preconditionError(\n `The \"entry\" in ekanos.json contains characters that cannot appear in ` +\n `an import specifier: ${JSON.stringify(specifier)}.`,\n 'Rename the file so its path has no quotes, backslashes or control ' +\n 'characters, then update \"entry\" in ekanos.json to match.',\n );\n }\n\n return JSON.stringify(`./${specifier}`);\n}\n\nexport interface BootstrapConfigResult {\n /** True only when this run created the file. */\n bootstrapped: boolean;\n configPath: string;\n}\n\n/**\n * Write a starter `harness.config.ts` at the project root — ONCE.\n *\n * An existing file is left strictly alone, byte for byte, and no flag changes\n * that: it is the one harness file the partner owns, it is git-tracked, and\n * regenerating it would throw away their fixtures. `--force` regenerates the\n * shell, never this.\n */\nexport function bootstrapHarnessConfig(\n projectDir: string,\n integrations: readonly { slug: string; entry: string }[],\n): BootstrapConfigResult {\n const configPath = path.join(projectDir, HARNESS_CONFIG_FILENAME);\n const kind = classifyPath(configPath);\n\n // A symlink here — including a dangling one, which `existsSync` reports as\n // absent — would have us write through to its target. Since this file is the\n // partner's to own, the only safe answer is to touch nothing and say why.\n if (kind === 'symlink') {\n throw invalidStateError(\n `${configPath} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Replace ${configPath} with a real file (or delete it to have one ` +\n 'generated), then re-run \"ekanos dev\".',\n );\n }\n\n if (kind !== 'missing') {\n return { bootstrapped: false, configPath };\n }\n\n writeFileNoFollow(projectDir, configPath, renderHarnessConfig(integrations));\n\n return { bootstrapped: true, configPath };\n}\n\n/**\n * Render the starter registry for EVERY integration the project declares.\n *\n * The harness registry is an array and `ekanos.json` can now hold a list, so a\n * project's second integration reaches the harness the same way its first\n * does. Each entry imports the definition under a distinct local identifier\n * derived from its slug, because two `integration` imports in one module do\n * not compile.\n */\nfunction renderHarnessConfig(\n integrations: readonly { slug: string; entry: string }[],\n): string {\n const imports: string[] = [];\n const entries: string[] = [];\n\n for (const integration of integrations) {\n const ident = identifierForSlug(integration.slug);\n imports.push(\n `import { integration as ${ident} } from ${entryImportLiteral(\n integration.entry,\n )};`,\n );\n entries.push(\n readTemplate('harness.config.entry.ts.tmpl', {\n IDENT: ident,\n SLUG: integration.slug,\n }),\n );\n }\n\n return readTemplate('harness.config.ts.tmpl', {\n INTEGRATION_IMPORTS: imports.join('\\n'),\n INTEGRATION_ENTRIES: entries.join(''),\n });\n}\n\n/**\n * \"acme-crm\" → \"acmeCrm\". A slug is kebab-case by schema, so this only has to\n * handle hyphens, and a leading digit is impossible.\n */\nexport function identifierForSlug(slug: string): string {\n const camel = slug.replace(/-([a-z0-9])/g, (_, char: string) =>\n char.toUpperCase(),\n );\n return /^[0-9]/.test(camel) ? `i${camel}` : camel;\n}\n\n/**\n * Add `.ekanos/` to the project `.gitignore`, exactly once.\n *\n * Matching is line-wise over the spellings git treats as the same rule\n * (`.ekanos`, `.ekanos/`, `/.ekanos/`), so re-running never appends a\n * duplicate — the thing that makes a scaffold verb safe to run in a loop.\n * A negation (`!.ekanos/`) is deliberately NOT treated as a match: the\n * partner meant to un-ignore it, and we do not fight that silently.\n */\nexport function ensureGitignoreEntry(projectDir: string): boolean {\n const gitignorePath = path.join(projectDir, '.gitignore');\n const block = `# Ekanos dev harness — generated by \"ekanos dev\", never edited by hand\\n${GITIGNORE_ENTRY}\\n`;\n\n const kind = classifyPath(gitignorePath);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${gitignorePath} is a symlink, so \"ekanos dev\" will not append to it.`,\n `Add \"${GITIGNORE_ENTRY}\" to your real .gitignore yourself, or replace ` +\n `${gitignorePath} with a regular file, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') {\n writeFileNoFollow(projectDir, gitignorePath, block);\n return true;\n }\n\n const existing = readFileNoFollow(projectDir, gitignorePath) ?? '';\n if (gitignoreCovers(existing)) return false;\n\n const separator =\n existing.length === 0 || existing.endsWith('\\n') ? '' : '\\n';\n appendFileNoFollow(projectDir, gitignorePath, `${separator}\\n${block}`);\n return true;\n}\n\nfunction gitignoreCovers(source: string): boolean {\n const equivalents = new Set(['.ekanos', '.ekanos/', '/.ekanos', '/.ekanos/']);\n return source\n .split('\\n')\n .map((line) => line.trim())\n .some((line) => equivalents.has(line));\n}\n"]}
1
+ {"version":3,"file":"harness-scaffold.js","sourceRoot":"","sources":["../src/harness-scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { cliVersion, templatesFingerprint } from './compatibility';\nimport { invalidStateError, preconditionError } from './errors';\nimport {\n appendFileNoFollow,\n classifyPath,\n readFileNoFollow,\n writeFileNoFollow,\n} from './safe-fs';\nimport { readTemplate } from './templates';\n\n/** The dependency that owns every line of harness behaviour. */\nexport const HARNESS_PACKAGE = '@ekanos/harness';\n\n/** Where the generated shell lives, relative to the project root. */\nexport const HARNESS_DIR = path.join('.ekanos', 'harness');\n\n/** The single `.gitignore` line that covers the whole generated tree. */\nexport const GITIGNORE_ENTRY = '.ekanos/';\n\n/**\n * The staleness marker, inside the generated tree so deleting the tree also\n * deletes the marker. Holds the `@ekanos/harness` version that generated the\n * shell — a version bump means the shell may be wired against an older export\n * surface, so it is regenerated.\n */\n/** Next's dev/build cache inside the generated shell. */\nexport const BUILD_CACHE_DIR = '.next';\n\nexport const VERSION_MARKER = '.ekanos-version';\n\n/** The partner-owned registry, at the PROJECT ROOT. Never regenerated. */\nexport const HARNESS_CONFIG_FILENAME = 'harness.config.ts';\n\n/**\n * Every file in the generated shell: bundled template → path inside\n * `.ekanos/harness/`. The ten route files are mechanically derivable from the\n * export map alone — which is the test of whether the package boundary is in\n * the right place. If a shell file ever needs a judgment call, the boundary\n * has moved, not the template.\n *\n * `app/api/integrations/[slug]/[...path]/route.ts` is the shell's one SERVER\n * route: it injects the registry into `@ekanos/harness/api`, which serves the\n * live-mode OAuth loop, storage bridge and trigger route over a server-held\n * context. It is the third registry injection point, beside the two pages.\n *\n * `app/harness-shell.tsx` exists because a `HarnessIntegration` holds React\n * component references and MCP `run` functions. Next cannot serialize those\n * across a server→client boundary, so the `'use client'` mark has to sit ABOVE\n * the point where the registry is injected — hence a client shell of its own\n * rather than a client `RootLayout` called from a server layout. Keeping\n * `app/page.tsx` a server component is what lets `/` prerender static.\n */\nexport const SHELL_FILES: readonly { template: string; dest: string }[] = [\n { template: 'harness/app/layout.tsx.tmpl', dest: 'app/layout.tsx' },\n {\n template: 'harness/app/harness-shell.tsx.tmpl',\n dest: 'app/harness-shell.tsx',\n },\n { template: 'harness/app/page.tsx.tmpl', dest: 'app/page.tsx' },\n {\n template: 'harness/app/[slug]/layout.tsx.tmpl',\n dest: 'app/[slug]/layout.tsx',\n },\n {\n template: 'harness/app/[slug]/page.tsx.tmpl',\n dest: 'app/[slug]/page.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/page.tsx',\n },\n {\n template: 'harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl',\n dest: 'app/[slug]/widgets/[widgetId]/page.tsx',\n },\n {\n template: 'harness/app/[slug]/tile/page.tsx.tmpl',\n dest: 'app/[slug]/tile/page.tsx',\n },\n {\n template: 'harness/app/[slug]/activation/page.tsx.tmpl',\n dest: 'app/[slug]/activation/page.tsx',\n },\n {\n template: 'harness/app/[slug]/triggers/page.tsx.tmpl',\n dest: 'app/[slug]/triggers/page.tsx',\n },\n {\n template: 'harness/app/api/integrations/[slug]/[...path]/route.ts.tmpl',\n dest: 'app/api/integrations/[slug]/[...path]/route.ts',\n },\n { template: 'harness/next.config.mjs.tmpl', dest: 'next.config.mjs' },\n { template: 'harness/postcss.config.mjs.tmpl', dest: 'postcss.config.mjs' },\n { template: 'harness/tsconfig.json.tmpl', dest: 'tsconfig.json' },\n { template: 'harness/styles/globals.css.tmpl', dest: 'styles/globals.css' },\n { template: 'harness/package.json.tmpl', dest: 'package.json' },\n {\n template: 'harness/types/team-accounts.d.ts.tmpl',\n dest: 'types/team-accounts.d.ts',\n },\n];\n\n/**\n * Packages the shell needs to resolve upward out of the partner's own\n * `node_modules`. The shell declares no dependencies of its own by design, so\n * a missing one shows up as an opaque Next resolution error at dev-server\n * start; checking here turns that into one precondition with one fix.\n */\nexport const REQUIRED_DEPENDENCIES = [\n HARNESS_PACKAGE,\n 'next',\n 'react',\n 'react-dom',\n 'tailwindcss',\n '@tailwindcss/postcss',\n] as const;\n\nexport interface WrittenFiles {\n created: string[];\n updated: string[];\n unchanged: string[];\n}\n\n/**\n * Read an installed package's `package.json` out of a project's TOP-LEVEL\n * `node_modules`. Resolved by direct path join rather than `require.resolve`\n * on purpose, for two independent reasons:\n *\n * - The CLI runs from its own install and must read the version of the copy\n * in THE CALLER'S project, not one hoisted next to itself.\n * - Several of the packages this reads (`@ekanos/integration-schema`\n * included) declare an `exports` map with no `./package.json` subpath, so\n * `require.resolve('<pkg>/package.json')` throws\n * `ERR_PACKAGE_PATH_NOT_EXPORTED` under Node's ESM resolver. A plain file\n * read is not subject to the export map at all.\n *\n * Shared by every \"what does this project have installed?\" check in the\n * CLI — `readInstalledHarness` below, the schema-skew gate, and `upgrade`'s\n * current-toolchain report — so there is exactly one implementation of \"read\n * a package's on-disk version\", not a subtly different one per call site.\n */\nexport interface InstalledPackage {\n version: string;\n /** The raw package.json, for callers that need more than the version. */\n manifest: unknown;\n}\n\nexport function readInstalledPackageManifest(\n projectDir: string,\n packageName: string,\n): InstalledPackage | null {\n const manifestPath = path.join(\n projectDir,\n 'node_modules',\n ...packageName.split('/'),\n 'package.json',\n );\n if (!fs.existsSync(manifestPath)) return null;\n\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n const version =\n typeof parsed === 'object' && parsed !== null\n ? (parsed as { version?: unknown }).version\n : undefined;\n if (typeof version !== 'string' || version.length === 0) return null;\n return { version, manifest: parsed };\n } catch (error) {\n throw preconditionError(\n `${packageName}'s package.json in node_modules is unreadable: ${\n error instanceof Error ? error.message : String(error)\n }`,\n `Reinstall dependencies in ${projectDir} so ${packageName} resolves.`,\n );\n }\n}\n\n/** Read the installed `@ekanos/harness` version out of the partner's project. */\nexport type InstalledHarness = InstalledPackage;\n\nexport function readInstalledHarness(\n projectDir: string,\n): InstalledHarness | null {\n return readInstalledPackageManifest(projectDir, HARNESS_PACKAGE);\n}\n\n/** Just the installed version, for callers that need nothing else. */\nexport function readInstalledHarnessVersion(projectDir: string): string | null {\n return readInstalledHarness(projectDir)?.version ?? null;\n}\n\n/** Which of `REQUIRED_DEPENDENCIES` are not installed in the project root. */\nexport function missingDependencies(projectDir: string): string[] {\n return REQUIRED_DEPENDENCIES.filter(\n (name) =>\n !fs.existsSync(\n path.join(projectDir, 'node_modules', name, 'package.json'),\n ),\n );\n}\n\n/**\n * What generated an existing shell.\n *\n * All three fields matter, and recording only `harness` was a real hole: the\n * templates ship with the CLI, so a shell can be stale because the CLI changed\n * while the harness did not. `templates` is the authoritative one — it is a\n * hash of the template sources, so it moves whenever what we generate moves,\n * including for a CLI build that never shipped a version bump.\n */\nexport interface ShellMarker {\n harness: string;\n /** null for a legacy bare-version marker written before this was recorded. */\n cli: string | null;\n templates: string | null;\n}\n\n/**\n * Three outcomes, not two, because \"no marker\" and \"unreadable marker\" call\n * for opposite responses.\n *\n * A marker's PRESENCE is itself evidence the tree is ours — a squatter does\n * not write `.ekanos-version` — and the likeliest cause of an unreadable one\n * is our own interrupted write. Everything under `.ekanos/harness/` is\n * CLI-owned, gitignored and reproducible from the templates, so regenerating\n * destroys nothing: there is no value at risk to justify charging a partner a\n * confusing `--force` to recover from our own bad write.\n *\n * An ABSENT marker over a non-empty tree is the opposite: nothing says the\n * files are ours, so we refuse. That is the L1 rule and it stays.\n */\nexport type MarkerState =\n | { kind: 'missing' }\n | { kind: 'corrupt' }\n | { kind: 'ok'; marker: ShellMarker };\n\nexport function readShellMarkerState(harnessDir: string): MarkerState {\n const marker = path.join(harnessDir, VERSION_MARKER);\n if (classifyPath(marker) !== 'file') return { kind: 'missing' };\n\n const raw = (readFileNoFollow(harnessDir, marker) ?? '').trim();\n if (raw.length === 0) return { kind: 'corrupt' };\n\n const parsed = parseMarker(raw);\n return parsed === null ? { kind: 'corrupt' } : { kind: 'ok', marker: parsed };\n}\n\nfunction parseMarker(raw: string): ShellMarker | null {\n // v1 markers were a bare version string. Read them rather than crashing, and\n // report the unknown fields as null so the shell counts as stale and is\n // regenerated once — which is the correct outcome for an upgrade.\n //\n // It must actually LOOK like a version: without this, any garbage that does\n // not happen to start with `{` — a truncated write, a stray word — reads as\n // a legacy marker named after the garbage, and the corrupt-marker path never\n // fires. That is how a truncated `.ekanos-version` was silently reported as\n // \"@ekanos/harness truncat → 0.1.0\" instead of \"unreadable\".\n if (!raw.startsWith('{')) {\n return /^\\d+\\.\\d+/.test(raw)\n ? { harness: raw, cli: null, templates: null }\n : null;\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null) return null;\n const record = parsed as Record<string, unknown>;\n const harness = record.harness;\n if (typeof harness !== 'string') return null;\n return {\n harness,\n cli: typeof record.cli === 'string' ? record.cli : null,\n templates: typeof record.templates === 'string' ? record.templates : null,\n };\n } catch {\n return null;\n }\n}\n\n/** The parsed marker, or null when it is absent OR unreadable. */\nexport function readShellMarker(harnessDir: string): ShellMarker | null {\n const state = readShellMarkerState(harnessDir);\n return state.kind === 'ok' ? state.marker : null;\n}\n\n/** The `@ekanos/harness` version an existing shell was generated against. */\nexport function readShellVersion(harnessDir: string): string | null {\n return readShellMarker(harnessDir)?.harness ?? null;\n}\n\nfunction serializeMarker(marker: ShellMarker): string {\n return `${JSON.stringify(marker, null, 2)}\\n`;\n}\n\n/**\n * Write a generated file, but ONLY when its bytes would change.\n *\n * This is what makes a re-run cheap and a watcher quiet: an unchanged file is\n * not rewritten, so its mtime does not move and Next's dev server does not\n * rebuild. It also means a hand-edit inside the CLI-owned tree is silently\n * corrected on the next run, which is the intended contract for `.ekanos/`.\n */\nexport function writeIfChanged(\n root: string,\n destPath: string,\n contents: string,\n into: WrittenFiles,\n label: string,\n force: boolean,\n): void {\n // `classifyPath` uses lstat, so a symlink here is reported as a symlink\n // rather than as whatever it points at — including a DANGLING one, which\n // `existsSync` would call absent and a plain write would then materialize\n // outside the project.\n const kind = classifyPath(destPath);\n const exists = kind !== 'missing';\n\n if (\n !force &&\n kind === 'file' &&\n readFileNoFollow(root, destPath) === contents\n ) {\n into.unchanged.push(label);\n return;\n }\n\n writeFileNoFollow(root, destPath, contents);\n (exists ? into.updated : into.created).push(label);\n}\n\nexport interface ScaffoldShellArgs {\n projectDir: string;\n slug: string;\n harnessVersion: string;\n /** Rewrite every file even when its bytes already match. */\n force: boolean;\n /** Extra Tailwind @source globs from ekanos.json, already validated. */\n sourceGlobs?: readonly string[];\n}\n\nexport interface ScaffoldShellResult extends WrittenFiles {\n harnessDir: string;\n /** The version the shell carried before this run — null on a first run. */\n previousVersion: string | null;\n /** What generated the previous shell, or null on a first run. */\n previousMarker: ShellMarker | null;\n /** True when anything that generated the shell has since changed. */\n stale: boolean;\n /** `.next` under the shell was deleted because the shell was stale. */\n buildCacheCleared: boolean;\n /**\n * Which of harness / cli / templates moved. Empty on a first run; this is\n * what lets `dev` say WHY it regenerated rather than just that it did.\n */\n staleReasons: string[];\n}\n\n/**\n * Generate `.ekanos/harness/**` for `harnessVersion`. Every file here is\n * CLI-owned and gitignored, so regeneration needs no permission and asks no\n * question — the partner owns `harness.config.ts` and their integration\n * source, and nothing else.\n */\nexport function scaffoldShell(args: ScaffoldShellArgs): ScaffoldShellResult {\n const harnessDir = path.join(args.projectDir, HARNESS_DIR);\n assertShellDirIsOurs(harnessDir, args.force);\n\n const markerState = readShellMarkerState(harnessDir);\n const previousMarker = markerState.kind === 'ok' ? markerState.marker : null;\n const previousVersion = previousMarker?.harness ?? null;\n const marker: ShellMarker = {\n harness: args.harnessVersion,\n cli: cliVersion(),\n templates: templatesFingerprint(SHELL_FILES.map((f) => f.template)),\n };\n\n // A shell is current only if EVERYTHING that produced it is unchanged.\n // Checking the harness version alone certified shells generated by an older\n // CLI, against templates that had since moved, as up to date.\n const staleReasons: string[] = [];\n if (markerState.kind === 'corrupt') {\n staleReasons.push(\n `${VERSION_MARKER} was unreadable, so the existing shell could not be ` +\n 'certified',\n );\n }\n if (previousMarker !== null) {\n if (previousMarker.harness !== marker.harness) {\n staleReasons.push(\n `@ekanos/harness ${previousMarker.harness} → ${marker.harness}`,\n );\n }\n if (previousMarker.cli !== marker.cli) {\n staleReasons.push(\n `@ekanos/cli ${previousMarker.cli ?? 'unknown'} → ${marker.cli}`,\n );\n }\n if (previousMarker.templates !== marker.templates) {\n staleReasons.push('the shell templates changed');\n }\n }\n const stale = staleReasons.length > 0;\n\n // Next's persistent dev cache was built from the OLD shell and the OLD\n // toolchain. Keeping it across a regeneration served a corrupted Tailwind\n // candidate on the 0.1 → 0.4 jump (every page a build-error overlay until\n // someone deleted the directory by hand). The shell knows it is stale; the\n // cache goes with it.\n const buildCacheDir = path.join(harnessDir, BUILD_CACHE_DIR);\n const buildCacheCleared = stale && fs.existsSync(buildCacheDir);\n if (buildCacheCleared) {\n fs.rmSync(buildCacheDir, { recursive: true, force: true });\n }\n\n const files: WrittenFiles = { created: [], updated: [], unchanged: [] };\n const vars = {\n SLUG: args.slug,\n SOURCE_GLOBS: renderSourceGlobs(args.sourceGlobs ?? []),\n };\n\n for (const { template, dest } of SHELL_FILES) {\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, dest),\n readTemplate(template, vars),\n files,\n dest,\n args.force,\n );\n }\n\n writeIfChanged(\n args.projectDir,\n path.join(harnessDir, VERSION_MARKER),\n serializeMarker(marker),\n files,\n VERSION_MARKER,\n args.force,\n );\n\n return {\n ...files,\n harnessDir,\n previousVersion,\n previousMarker,\n stale,\n staleReasons,\n buildCacheCleared,\n };\n}\n\n/**\n * Turn validated `sourceGlobs` into the `@source` lines the generated\n * stylesheet carries, each relative to the shell's `styles/` directory.\n *\n * Tailwind never scans what it is not pointed at, and a glob that matches\n * nothing fails silently — so an integration living outside the default\n * layout renders completely unstyled with no error anywhere. That is what\n * `sourceGlobs` is for, and until now it was parsed and thrown away.\n */\nfunction renderSourceGlobs(globs: readonly string[]): string {\n if (globs.length === 0) return '';\n const lines = globs.map((glob) => `@source '${SHELL_TO_PROJECT}${glob}';`);\n return `\\n/* From \"sourceGlobs\" in ekanos.json. */\\n${lines.join('\\n')}\\n`;\n}\n\n/** From `.ekanos/harness/styles/` back up to the project root. */\nconst SHELL_TO_PROJECT = '../../../';\n\n/**\n * Establish that `.ekanos/harness` is a tree WE generated before writing a\n * single byte into it, and return the version marker it carried.\n *\n * Path location alone is not ownership. A directory that exists but carries no\n * `.ekanos-version` was put there by something else — a partner, another tool,\n * a symlink — and clobbering it because it happens to sit at the path we like\n * is the same class of mistake as following a symlink. An empty directory is\n * fine (that is just `mkdir -p` having run), and `--force` is the explicit\n * override.\n *\n * Runs BEFORE `bootstrapHarnessConfig` in `dev`, so a shell that cannot be\n * written leaves the project completely untouched rather than half-scaffolded.\n */\nexport function assertShellDirIsOurs(\n harnessDir: string,\n force: boolean,\n): string | null {\n const kind = classifyPath(harnessDir);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${harnessDir} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Delete ${harnessDir} and re-run \"ekanos dev\" — the shell is generated, ` +\n 'so nothing is lost.',\n );\n }\n\n if (kind !== 'missing' && kind !== 'directory') {\n throw invalidStateError(\n `${harnessDir} exists and is not a directory.`,\n `Move or delete ${harnessDir}, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') return null;\n\n const state = readShellMarkerState(harnessDir);\n\n // A marker we wrote, however mangled, still identifies the tree as ours.\n if (state.kind === 'ok') return state.marker.harness;\n if (state.kind === 'corrupt' || force) return null;\n\n if (fs.readdirSync(harnessDir).length === 0) return null;\n\n throw invalidStateError(\n `${harnessDir} already contains files but no ${VERSION_MARKER} marker, ` +\n 'so it was not generated by \"ekanos dev\".',\n `Delete ${harnessDir} if you do not need it, or pass --force to ` +\n 'overwrite it. Everything \"ekanos dev\" generates is reproducible; ' +\n 'anything else in there is not.',\n );\n}\n\n/**\n * The module specifier `harness.config.ts` uses to import the partner's\n * integration: the `entry` from ekanos.json, made relative and extension-free.\n * \"src/integration.ts\" → \"src/integration\".\n */\nexport function entryImportSpecifier(entry: string): string {\n const normalized = entry.split(path.sep).join('/').replace(/^\\.\\//, '');\n return normalized.replace(/\\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, '');\n}\n\n/**\n * The same specifier as a JavaScript string LITERAL, ready to drop into a\n * generated `import` statement.\n *\n * `entry` is attacker-influenced data from `ekanos.json`, and a POSIX filename\n * may legally contain a quote or a newline. Interpolating it raw would let a\n * crafted-but-valid path close the string and append statements to the module\n * we generate. `JSON.stringify` produces a correctly escaped literal, which is\n * the actual fix; the explicit rejection below is a second line that also\n * gives the partner a comprehensible error instead of a bizarre filename\n * silently appearing, escaped, in their config.\n *\n * This does not cross a trust boundary on its own — a partner's own code runs\n * unsandboxed anyway — but \"the templates cannot be injected into\" is a claim\n * we make, and it should be true.\n */\nexport function entryImportLiteral(entry: string): string {\n const specifier = entryImportSpecifier(entry);\n\n // eslint-disable-next-line no-control-regex\n if (/[\"'`\\\\\\n\\r\\u0000-\\u001f]/.test(specifier)) {\n throw preconditionError(\n `The \"entry\" in ekanos.json contains characters that cannot appear in ` +\n `an import specifier: ${JSON.stringify(specifier)}.`,\n 'Rename the file so its path has no quotes, backslashes or control ' +\n 'characters, then update \"entry\" in ekanos.json to match.',\n );\n }\n\n return JSON.stringify(`./${specifier}`);\n}\n\nexport interface BootstrapConfigResult {\n /** True only when this run created the file. */\n bootstrapped: boolean;\n configPath: string;\n}\n\n/**\n * Write a starter `harness.config.ts` at the project root — ONCE.\n *\n * An existing file is left strictly alone, byte for byte, and no flag changes\n * that: it is the one harness file the partner owns, it is git-tracked, and\n * regenerating it would throw away their fixtures. `--force` regenerates the\n * shell, never this.\n */\nexport function bootstrapHarnessConfig(\n projectDir: string,\n integrations: readonly { slug: string; entry: string }[],\n): BootstrapConfigResult {\n const configPath = path.join(projectDir, HARNESS_CONFIG_FILENAME);\n const kind = classifyPath(configPath);\n\n // A symlink here — including a dangling one, which `existsSync` reports as\n // absent — would have us write through to its target. Since this file is the\n // partner's to own, the only safe answer is to touch nothing and say why.\n if (kind === 'symlink') {\n throw invalidStateError(\n `${configPath} is a symlink, so \"ekanos dev\" will not write through it.`,\n `Replace ${configPath} with a real file (or delete it to have one ` +\n 'generated), then re-run \"ekanos dev\".',\n );\n }\n\n if (kind !== 'missing') {\n return { bootstrapped: false, configPath };\n }\n\n writeFileNoFollow(projectDir, configPath, renderHarnessConfig(integrations));\n\n return { bootstrapped: true, configPath };\n}\n\n/**\n * Render the starter registry for EVERY integration the project declares.\n *\n * The harness registry is an array and `ekanos.json` can now hold a list, so a\n * project's second integration reaches the harness the same way its first\n * does. Each entry imports the definition under a distinct local identifier\n * derived from its slug, because two `integration` imports in one module do\n * not compile.\n */\nfunction renderHarnessConfig(\n integrations: readonly { slug: string; entry: string }[],\n): string {\n const imports: string[] = [];\n const entries: string[] = [];\n\n for (const integration of integrations) {\n const ident = identifierForSlug(integration.slug);\n imports.push(\n `import { integration as ${ident} } from ${entryImportLiteral(\n integration.entry,\n )};`,\n );\n entries.push(\n readTemplate('harness.config.entry.ts.tmpl', {\n IDENT: ident,\n SLUG: integration.slug,\n }),\n );\n }\n\n return readTemplate('harness.config.ts.tmpl', {\n INTEGRATION_IMPORTS: imports.join('\\n'),\n INTEGRATION_ENTRIES: entries.join(''),\n });\n}\n\n/**\n * \"acme-crm\" → \"acmeCrm\". A slug is kebab-case by schema, so this only has to\n * handle hyphens, and a leading digit is impossible.\n */\nexport function identifierForSlug(slug: string): string {\n const camel = slug.replace(/-([a-z0-9])/g, (_, char: string) =>\n char.toUpperCase(),\n );\n return /^[0-9]/.test(camel) ? `i${camel}` : camel;\n}\n\n/**\n * Add `.ekanos/` to the project `.gitignore`, exactly once.\n *\n * Matching is line-wise over the spellings git treats as the same rule\n * (`.ekanos`, `.ekanos/`, `/.ekanos/`), so re-running never appends a\n * duplicate — the thing that makes a scaffold verb safe to run in a loop.\n * A negation (`!.ekanos/`) is deliberately NOT treated as a match: the\n * partner meant to un-ignore it, and we do not fight that silently.\n */\nexport function ensureGitignoreEntry(projectDir: string): boolean {\n const gitignorePath = path.join(projectDir, '.gitignore');\n const block = `# Ekanos dev harness — generated by \"ekanos dev\", never edited by hand\\n${GITIGNORE_ENTRY}\\n`;\n\n const kind = classifyPath(gitignorePath);\n\n if (kind === 'symlink') {\n throw invalidStateError(\n `${gitignorePath} is a symlink, so \"ekanos dev\" will not append to it.`,\n `Add \"${GITIGNORE_ENTRY}\" to your real .gitignore yourself, or replace ` +\n `${gitignorePath} with a regular file, then re-run \"ekanos dev\".`,\n );\n }\n\n if (kind === 'missing') {\n writeFileNoFollow(projectDir, gitignorePath, block);\n return true;\n }\n\n const existing = readFileNoFollow(projectDir, gitignorePath) ?? '';\n if (gitignoreCovers(existing)) return false;\n\n const separator =\n existing.length === 0 || existing.endsWith('\\n') ? '' : '\\n';\n appendFileNoFollow(projectDir, gitignorePath, `${separator}\\n${block}`);\n return true;\n}\n\nfunction gitignoreCovers(source: string): boolean {\n const equivalents = new Set(['.ekanos', '.ekanos/', '/.ekanos', '/.ekanos/']);\n return source\n .split('\\n')\n .map((line) => line.trim())\n .some((line) => equivalents.has(line));\n}\n"]}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { runUse } from './commands/use.js';
12
12
  import { runValidate } from './commands/validate.js';
13
13
  import { runWhoami } from './commands/whoami.js';
14
14
  import { CliContext } from './context.js';
15
+ import { SDK_DOCS_URL, docsPage } from './docs-links.js';
15
16
  import { toCliError, usageError } from './errors.js';
16
17
  import { EXIT_CODES } from './exit-codes.js';
17
18
  import { parseArgv } from './parse-argv.js';
@@ -387,6 +388,7 @@ function emitVersion(ctx) {
387
388
  const version = readVersion();
388
389
  return ctx.succeed({ name: 'ekanos', version }, version);
389
390
  }
391
+ const DOCS_FOOTER = `\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;
390
392
  function emitHelp(ctx) {
391
393
  ctx.emitClaudeCodeHint({
392
394
  tool: 'ekanos',
@@ -394,7 +396,7 @@ function emitHelp(ctx) {
394
396
  usage: USAGE,
395
397
  exitCodes: EXIT_CODES,
396
398
  });
397
- return ctx.succeed({ usage: USAGE, commands: COMMANDS }, USAGE);
399
+ return ctx.succeed({ usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL }, `${USAGE}${DOCS_FOOTER}`);
398
400
  }
399
401
  function emitCommandHelp(ctx, command) {
400
402
  var _a;