@agent-native/core 0.80.1 → 0.80.4

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 (59) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +65 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/action.ts +11 -0
  5. package/corpus/core/src/agent/production-agent.ts +3 -0
  6. package/corpus/core/src/cli/plan-local.ts +445 -108
  7. package/corpus/core/src/cli/skills.ts +14 -5
  8. package/corpus/core/src/mcp/build-server.ts +1 -1
  9. package/corpus/core/src/mcp/embed-app.ts +35 -1
  10. package/corpus/core/src/server/action-routes.ts +14 -0
  11. package/corpus/core/src/vite/client.ts +15 -1
  12. package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +162 -0
  13. package/corpus/templates/clips/app/i18n/ar-SA.ts +4 -0
  14. package/corpus/templates/clips/app/i18n/de-DE.ts +4 -0
  15. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  16. package/corpus/templates/clips/app/i18n/es-ES.ts +4 -0
  17. package/corpus/templates/clips/app/i18n/fr-FR.ts +4 -0
  18. package/corpus/templates/clips/app/i18n/hi-IN.ts +4 -0
  19. package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -0
  20. package/corpus/templates/clips/app/i18n/ko-KR.ts +4 -0
  21. package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -0
  22. package/corpus/templates/clips/app/i18n/zh-CN.ts +4 -0
  23. package/corpus/templates/clips/app/i18n/zh-TW.ts +4 -0
  24. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +42 -18
  25. package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +14 -5
  26. package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +14 -5
  27. package/corpus/templates/plan/actions/validate-local-plan-source.ts +118 -0
  28. package/corpus/templates/plan/server/lib/plan-connector-catalog.ts +1 -0
  29. package/corpus/templates/plan/server/lib/public-action-paths.ts +1 -0
  30. package/dist/action.d.ts +8 -0
  31. package/dist/action.d.ts.map +1 -1
  32. package/dist/action.js +3 -0
  33. package/dist/action.js.map +1 -1
  34. package/dist/agent/production-agent.d.ts +3 -0
  35. package/dist/agent/production-agent.d.ts.map +1 -1
  36. package/dist/agent/production-agent.js.map +1 -1
  37. package/dist/cli/plan-local.d.ts +30 -0
  38. package/dist/cli/plan-local.d.ts.map +1 -1
  39. package/dist/cli/plan-local.js +281 -56
  40. package/dist/cli/plan-local.js.map +1 -1
  41. package/dist/cli/skills.d.ts +1 -1
  42. package/dist/cli/skills.d.ts.map +1 -1
  43. package/dist/cli/skills.js +14 -5
  44. package/dist/cli/skills.js.map +1 -1
  45. package/dist/collab/routes.d.ts +1 -1
  46. package/dist/mcp/build-server.js +1 -1
  47. package/dist/mcp/build-server.js.map +1 -1
  48. package/dist/mcp/embed-app.d.ts.map +1 -1
  49. package/dist/mcp/embed-app.js +35 -1
  50. package/dist/mcp/embed-app.js.map +1 -1
  51. package/dist/resources/handlers.d.ts +1 -1
  52. package/dist/server/action-routes.d.ts.map +1 -1
  53. package/dist/server/action-routes.js +14 -0
  54. package/dist/server/action-routes.js.map +1 -1
  55. package/dist/server/transcribe-voice.d.ts +1 -1
  56. package/dist/vite/client.d.ts.map +1 -1
  57. package/dist/vite/client.js +12 -1
  58. package/dist/vite/client.js.map +1 -1
  59. package/package.json +1 -1
@@ -111,6 +111,20 @@ export type LocalPlanBridgeServer = {
111
111
  server: Server;
112
112
  result: LocalPlanServeResult;
113
113
  };
114
+ export type RendererValidationIssue = {
115
+ path: string;
116
+ message: string;
117
+ };
118
+ export type RendererValidation = {
119
+ /** Whether the renderer validate endpoint was reachable and answered. */
120
+ ran: boolean;
121
+ endpoint: string;
122
+ /** Renderer verdict — present only when `ran` is true. */
123
+ valid?: boolean;
124
+ issues?: RendererValidationIssue[];
125
+ /** Transport/endpoint error when `ran` is false (unreachable, 404, old deploy). */
126
+ error?: string;
127
+ };
114
128
  export type LocalPlanVerifyResult = {
115
129
  ok: boolean;
116
130
  dir: string;
@@ -135,6 +149,7 @@ export type LocalPlanVerifyResult = {
135
149
  mdxFiles?: string[];
136
150
  error?: string;
137
151
  };
152
+ validation: RendererValidation;
138
153
  warnings: string[];
139
154
  };
140
155
  type OpenLocalUrlResult = {
@@ -169,7 +184,22 @@ export declare function startLocalPlanBridge(input: {
169
184
  open?: boolean;
170
185
  urlFile?: string | false;
171
186
  openUrl?: (url: string) => OpenLocalUrlResult;
187
+ skipSourceValidation?: boolean;
172
188
  }): Promise<LocalPlanBridgeServer>;
189
+ /**
190
+ * Ask the Plan app to validate the folder against its real renderer schema
191
+ * (`parsePlanMdxFolder` + `planContentSchema`) via the public, no-DB
192
+ * `validate-local-plan-source` action. This is what makes `verify`
193
+ * authoritative: the hand-rolled `check` lint can disagree with the renderer,
194
+ * but this runs the renderer's own parser. Degrades gracefully (`ran: false`)
195
+ * when the endpoint is unreachable or a Plan app predates the action, so an
196
+ * old deploy never turns into a hard failure.
197
+ */
198
+ export declare function fetchRendererValidation(input: {
199
+ appUrl: string;
200
+ files: LocalPlanFiles;
201
+ fetchFn?: typeof fetch;
202
+ }): Promise<RendererValidation>;
173
203
  export declare function verifyLocalPlanBridge(input: {
174
204
  dir: string;
175
205
  kind?: LocalPlanKind;
@@ -1 +1 @@
1
- {"version":3,"file":"plan-local.d.ts","sourceRoot":"","sources":["../../src/cli/plan-local.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan-local.d.ts","sourceRoot":"","sources":["../../src/cli/plan-local.ts"],"names":[],"mappings":"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"}
@@ -711,10 +711,16 @@ function lintColumnsBlocks(file, source, issues) {
711
711
  const openingEnd = findJsxOpeningTagEnd(scanSource, start);
712
712
  if (openingEnd < 0)
713
713
  continue;
714
- const opening = scanSource.slice(start, openingEnd + 1);
715
- if (/\bcolumns\s*=/.test(opening)) {
716
- addValidationIssue(issues, file, source, start, 'Columns must use <Column> children, not a columns= prop. Use <Columns><Column label="Before">...</Column><Column label="After">...</Column></Columns>.');
717
- }
714
+ const opening = source.slice(start, openingEnd + 1);
715
+ const columnsAttr = readJsxAttribute(opening, "columns");
716
+ if (!columnsAttr)
717
+ continue;
718
+ const entries = columnsAttr.kind === "expression"
719
+ ? extractTopLevelObjectLiterals(columnsAttr.value)
720
+ : null;
721
+ if (entries && entries.length > 0)
722
+ continue;
723
+ addValidationIssue(issues, file, source, start, 'Columns must use <Column> children or a columns={[{ id, label, blocks }]} array, not a bare columns= prop. Use <Columns><Column label="Before">...</Column><Column label="After">...</Column></Columns>.');
718
724
  }
719
725
  }
720
726
  function findBalancedEnd(source, start, open, close) {
@@ -983,6 +989,56 @@ function hasRequiredEnumLiteral(objectSource, name, allowed) {
983
989
  return false;
984
990
  return allowed.includes(trimmed.slice(1, -1).trim());
985
991
  }
992
+ // Shared per-item checks so the top-level `<Checklist items={[…]} />` tag and
993
+ // blocks authored as JSON inside a container's `tabs={[…]}` / `columns={[…]}`
994
+ // array enforce the SAME required fields. `listBase` is the absolute offset of
995
+ // the items array in the original source so line numbers point near the item.
996
+ function checkChecklistItemList(items, listBase, file, source, issues) {
997
+ items.forEach((item, index) => {
998
+ const base = listBase + item.start;
999
+ if (!hasRequiredStaticId(item.source)) {
1000
+ addValidationIssue(issues, file, source, base, `Checklist items[${index}].id is required by the Plan renderer schema; add a stable string id.`);
1001
+ }
1002
+ if (!hasRequiredStaticString(item.source, "label")) {
1003
+ addValidationIssue(issues, file, source, base, `Checklist items[${index}].label is required by the Plan renderer schema; add a non-empty string label.`);
1004
+ }
1005
+ });
1006
+ }
1007
+ function checkQuestionObjectList(questionObjects, listBase, tag, file, source, issues) {
1008
+ questionObjects.forEach((question, questionIndex) => {
1009
+ const questionBase = listBase + question.start;
1010
+ if (!hasRequiredStaticId(question.source)) {
1011
+ addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
1012
+ }
1013
+ if (!hasRequiredStaticString(question.source, "title")) {
1014
+ addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].title is required by the Plan renderer schema; add a non-empty string title.`);
1015
+ }
1016
+ if (!hasRequiredEnumLiteral(question.source, "mode", [
1017
+ "single",
1018
+ "multi",
1019
+ "freeform",
1020
+ ])) {
1021
+ addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].mode is required by the Plan renderer schema; use "single", "multi", or "freeform".`);
1022
+ }
1023
+ const options = readTopLevelObjectProperty(question.source, "options");
1024
+ if (!options)
1025
+ return;
1026
+ const optionObjects = extractTopLevelObjectLiterals(options.value);
1027
+ if (!optionObjects) {
1028
+ addValidationIssue(issues, file, source, questionBase + options.valueStart, `${tag} questions[${questionIndex}].options must be an inline array of object literals.`);
1029
+ return;
1030
+ }
1031
+ optionObjects.forEach((option, optionIndex) => {
1032
+ const optionBase = questionBase + options.valueStart + option.start;
1033
+ if (!hasRequiredStaticId(option.source)) {
1034
+ addValidationIssue(issues, file, source, optionBase, `${tag} questions[${questionIndex}].options[${optionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
1035
+ }
1036
+ if (!hasRequiredStaticString(option.source, "label")) {
1037
+ addValidationIssue(issues, file, source, optionBase, `${tag} questions[${questionIndex}].options[${optionIndex}].label is required by the Plan renderer schema; add a non-empty string label.`);
1038
+ }
1039
+ });
1040
+ });
1041
+ }
986
1042
  function lintChecklistShape(file, source, issues) {
987
1043
  const scanSource = maskCodeRegions(source);
988
1044
  const re = /<Checklist\b/g;
@@ -1005,15 +1061,7 @@ function lintChecklistShape(file, source, issues) {
1005
1061
  addValidationIssue(issues, file, source, start + items.start, "Checklist items must be an inline array of object literals so local check can validate the renderer schema.");
1006
1062
  continue;
1007
1063
  }
1008
- objects.forEach((item, index) => {
1009
- const base = start + items.start + item.start;
1010
- if (!hasRequiredStaticId(item.source)) {
1011
- addValidationIssue(issues, file, source, base, `Checklist items[${index}].id is required by the Plan renderer schema; add a stable string id.`);
1012
- }
1013
- if (!hasRequiredStaticString(item.source, "label")) {
1014
- addValidationIssue(issues, file, source, base, `Checklist items[${index}].label is required by the Plan renderer schema; add a non-empty string label.`);
1015
- }
1016
- });
1064
+ checkChecklistItemList(objects, start + items.start, file, source, issues);
1017
1065
  }
1018
1066
  }
1019
1067
  function lintQuestionFormShape(file, source, issues) {
@@ -1041,43 +1089,114 @@ function lintQuestionFormShape(file, source, issues) {
1041
1089
  addValidationIssue(issues, file, source, start + questions.start, `${tag} questions must be a non-empty inline array of object literals.`);
1042
1090
  continue;
1043
1091
  }
1044
- questionObjects.forEach((question, questionIndex) => {
1045
- const questionBase = start + questions.start + question.start;
1046
- if (!hasRequiredStaticId(question.source)) {
1047
- addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
1048
- }
1049
- if (!hasRequiredStaticString(question.source, "title")) {
1050
- addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].title is required by the Plan renderer schema; add a non-empty string title.`);
1051
- }
1052
- if (!hasRequiredEnumLiteral(question.source, "mode", [
1053
- "single",
1054
- "multi",
1055
- "freeform",
1056
- ])) {
1057
- addValidationIssue(issues, file, source, questionBase, `${tag} questions[${questionIndex}].mode is required by the Plan renderer schema; use "single", "multi", or "freeform".`);
1058
- }
1059
- const options = readTopLevelObjectProperty(question.source, "options");
1060
- if (!options)
1061
- return;
1062
- const optionObjects = extractTopLevelObjectLiterals(options.value);
1063
- if (!optionObjects) {
1064
- addValidationIssue(issues, file, source, start + questions.start + question.start + options.valueStart, `${tag} questions[${questionIndex}].options must be an inline array of object literals.`);
1065
- return;
1092
+ checkQuestionObjectList(questionObjects, start + questions.start, tag, file, source, issues);
1093
+ }
1094
+ }
1095
+ function staticStringLiteralValue(value) {
1096
+ if (value === undefined)
1097
+ return undefined;
1098
+ const trimmed = value.trim();
1099
+ const quote = trimmed[0];
1100
+ if (quote !== '"' && quote !== "'" && quote !== "`")
1101
+ return undefined;
1102
+ if (!trimmed.endsWith(quote) || trimmed.length < 2)
1103
+ return undefined;
1104
+ if (quote === "`" && /\$\{/.test(trimmed))
1105
+ return undefined;
1106
+ return trimmed.slice(1, -1);
1107
+ }
1108
+ // Container blocks authored with a JSON attribute array (`<TabsBlock tabs={[…]}
1109
+ // />`) carry their children as nested block objects, not top-level JSX tags, so
1110
+ // the tag-based linters above never inspect them — the exact "false green" the
1111
+ // renderer catches because it validates the whole parsed block tree. Walk those
1112
+ // nested `blocks: [...]` arrays and enforce the same per-block required fields,
1113
+ // recursing through nested containers. This is still a subset of the renderer
1114
+ // schema (which is why `verify` is the authority), but it closes the common
1115
+ // nested checklist / question-form / missing-id case offline.
1116
+ const NESTED_CONTAINER_TAGS = [
1117
+ { tag: "TabsBlock", attr: "tabs" },
1118
+ { tag: "Tabs", attr: "tabs" },
1119
+ { tag: "Columns", attr: "columns" },
1120
+ ];
1121
+ function lintNestedContainerEntry(entrySource, entryBase, file, source, issues) {
1122
+ const blocksProp = readTopLevelObjectProperty(entrySource, "blocks");
1123
+ if (!blocksProp)
1124
+ return;
1125
+ const blockObjects = extractTopLevelObjectLiterals(blocksProp.value);
1126
+ if (!blockObjects)
1127
+ return;
1128
+ const blocksBase = entryBase + blocksProp.valueStart;
1129
+ for (const block of blockObjects) {
1130
+ lintNestedBlock(block.source, blocksBase + block.start, file, source, issues);
1131
+ }
1132
+ }
1133
+ function lintNestedBlock(blockSource, blockBase, file, source, issues) {
1134
+ const typeProp = readTopLevelObjectProperty(blockSource, "type");
1135
+ const type = staticStringLiteralValue(typeProp?.value);
1136
+ if (!hasRequiredStaticId(blockSource)) {
1137
+ addValidationIssue(issues, file, source, blockBase, `Nested ${type ?? "block"} is missing a required string \`id\`; the Plan renderer schema validates every nested block.`);
1138
+ }
1139
+ const dataProp = readTopLevelObjectProperty(blockSource, "data");
1140
+ if (!dataProp)
1141
+ return;
1142
+ const dataBase = blockBase + dataProp.valueStart;
1143
+ if (type === "checklist") {
1144
+ const itemsProp = readTopLevelObjectProperty(dataProp.value, "items");
1145
+ if (!itemsProp)
1146
+ return;
1147
+ const items = extractTopLevelObjectLiterals(itemsProp.value);
1148
+ if (items) {
1149
+ checkChecklistItemList(items, dataBase + itemsProp.valueStart, file, source, issues);
1150
+ }
1151
+ return;
1152
+ }
1153
+ // visual-questions shares question-form's schema; lint both.
1154
+ if (type === "question-form" || type === "visual-questions") {
1155
+ const questionsProp = readTopLevelObjectProperty(dataProp.value, "questions");
1156
+ if (!questionsProp)
1157
+ return;
1158
+ const questionObjects = extractTopLevelObjectLiterals(questionsProp.value);
1159
+ if (questionObjects) {
1160
+ checkQuestionObjectList(questionObjects, dataBase + questionsProp.valueStart, type, file, source, issues);
1161
+ }
1162
+ return;
1163
+ }
1164
+ if (type === "tabs" || type === "columns") {
1165
+ const innerAttr = type === "tabs" ? "tabs" : "columns";
1166
+ const innerProp = readTopLevelObjectProperty(dataProp.value, innerAttr);
1167
+ if (!innerProp)
1168
+ return;
1169
+ const innerEntries = extractTopLevelObjectLiterals(innerProp.value);
1170
+ if (!innerEntries)
1171
+ return;
1172
+ const innerBase = dataBase + innerProp.valueStart;
1173
+ for (const entry of innerEntries) {
1174
+ lintNestedContainerEntry(entry.source, innerBase + entry.start, file, source, issues);
1175
+ }
1176
+ }
1177
+ }
1178
+ function lintNestedContainerBlocks(file, source, issues) {
1179
+ const scanSource = maskCodeRegions(source);
1180
+ for (const { tag, attr } of NESTED_CONTAINER_TAGS) {
1181
+ const re = new RegExp(`<${tag}\\b`, "g");
1182
+ let match;
1183
+ while ((match = re.exec(scanSource))) {
1184
+ const start = match.index;
1185
+ const openingEnd = findJsxOpeningTagEnd(scanSource, start);
1186
+ if (openingEnd < 0)
1187
+ continue;
1188
+ const opening = source.slice(start, openingEnd + 1);
1189
+ const attrValue = readJsxAttribute(opening, attr);
1190
+ if (!attrValue || attrValue.kind !== "expression")
1191
+ continue;
1192
+ const entries = extractTopLevelObjectLiterals(attrValue.value);
1193
+ if (!entries)
1194
+ continue;
1195
+ const entriesBase = start + attrValue.start;
1196
+ for (const entry of entries) {
1197
+ lintNestedContainerEntry(entry.source, entriesBase + entry.start, file, source, issues);
1066
1198
  }
1067
- optionObjects.forEach((option, optionIndex) => {
1068
- const optionBase = start +
1069
- questions.start +
1070
- question.start +
1071
- options.valueStart +
1072
- option.start;
1073
- if (!hasRequiredStaticId(option.source)) {
1074
- addValidationIssue(issues, file, source, optionBase, `${tag} questions[${questionIndex}].options[${optionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
1075
- }
1076
- if (!hasRequiredStaticString(option.source, "label")) {
1077
- addValidationIssue(issues, file, source, optionBase, `${tag} questions[${questionIndex}].options[${optionIndex}].label is required by the Plan renderer schema; add a non-empty string label.`);
1078
- }
1079
- });
1080
- });
1199
+ }
1081
1200
  }
1082
1201
  }
1083
1202
  // Blank out fenced code blocks and inline code spans (preserving newlines and
@@ -1098,6 +1217,7 @@ export function validateLocalPlanFiles(files) {
1098
1217
  lintColumnsBlocks(entry.file, source, issues);
1099
1218
  lintChecklistShape(entry.file, entry.source, issues);
1100
1219
  lintQuestionFormShape(entry.file, entry.source, issues);
1220
+ lintNestedContainerBlocks(entry.file, entry.source, issues);
1101
1221
  }
1102
1222
  return issues;
1103
1223
  }
@@ -1247,7 +1367,8 @@ export function writeLocalPlanPreview(input) {
1247
1367
  function buildLocalPlanBridgePayload(input) {
1248
1368
  const dir = path.resolve(input.dir);
1249
1369
  const files = readLocalPlanFiles(dir);
1250
- assertLocalPlanFilesValid(files);
1370
+ if (!input.skipSourceValidation)
1371
+ assertLocalPlanFilesValid(files);
1251
1372
  const parsed = stripFrontmatter(files.planMdx);
1252
1373
  const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
1253
1374
  const title = input.title ||
@@ -1317,6 +1438,7 @@ export async function startLocalPlanBridge(input) {
1317
1438
  kind: input.kind,
1318
1439
  title: input.title,
1319
1440
  brief: input.brief,
1441
+ skipSourceValidation: input.skipSourceValidation,
1320
1442
  });
1321
1443
  const token = input.token || crypto.randomBytes(24).toString("base64url");
1322
1444
  const host = input.host || "127.0.0.1";
@@ -1345,6 +1467,7 @@ export async function startLocalPlanBridge(input) {
1345
1467
  kind: input.kind,
1346
1468
  title: input.title,
1347
1469
  brief: input.brief,
1470
+ skipSourceValidation: input.skipSourceValidation,
1348
1471
  }));
1349
1472
  }
1350
1473
  catch (error) {
@@ -1418,8 +1541,88 @@ function localPlanBridgeWarnings(input) {
1418
1541
  }
1419
1542
  return warnings;
1420
1543
  }
1544
+ const VALIDATE_LOCAL_PLAN_ACTION = "validate-local-plan-source";
1545
+ /**
1546
+ * Ask the Plan app to validate the folder against its real renderer schema
1547
+ * (`parsePlanMdxFolder` + `planContentSchema`) via the public, no-DB
1548
+ * `validate-local-plan-source` action. This is what makes `verify`
1549
+ * authoritative: the hand-rolled `check` lint can disagree with the renderer,
1550
+ * but this runs the renderer's own parser. Degrades gracefully (`ran: false`)
1551
+ * when the endpoint is unreachable or a Plan app predates the action, so an
1552
+ * old deploy never turns into a hard failure.
1553
+ */
1554
+ export async function fetchRendererValidation(input) {
1555
+ const fetchFn = input.fetchFn ?? fetch;
1556
+ const endpoint = planActionEndpoint(input.appUrl, VALIDATE_LOCAL_PLAN_ACTION);
1557
+ try {
1558
+ const response = await fetchActionWithTimeout(endpoint, {
1559
+ method: "POST",
1560
+ headers: {
1561
+ accept: "application/json",
1562
+ "content-type": "application/json",
1563
+ },
1564
+ body: JSON.stringify({ mdx: localPlanMdxFolder(input.files) }),
1565
+ }, fetchFn);
1566
+ if (!response.ok) {
1567
+ const detail = await response.text().catch(() => "");
1568
+ return {
1569
+ ran: false,
1570
+ endpoint,
1571
+ error: `${VALIDATE_LOCAL_PLAN_ACTION} failed ${response.status} ${response.statusText}: ${sanitizeHttpDetail(detail, 300)}`,
1572
+ };
1573
+ }
1574
+ const json = (await response.json().catch(() => null));
1575
+ const body = json && isRecord(json.result) ? json.result : (json ?? undefined);
1576
+ if (!body || typeof body.valid !== "boolean") {
1577
+ return {
1578
+ ran: false,
1579
+ endpoint,
1580
+ error: `${VALIDATE_LOCAL_PLAN_ACTION} returned an unexpected payload.`,
1581
+ };
1582
+ }
1583
+ const issues = Array.isArray(body.issues)
1584
+ ? body.issues.filter(isRecord).map((issue) => ({
1585
+ path: typeof issue.path === "string" ? issue.path : "",
1586
+ message: typeof issue.message === "string" ? issue.message : "Invalid value",
1587
+ }))
1588
+ : [];
1589
+ return { ran: true, endpoint, valid: body.valid, issues };
1590
+ }
1591
+ catch (err) {
1592
+ return {
1593
+ ran: false,
1594
+ endpoint,
1595
+ error: err instanceof Error ? err.message : String(err),
1596
+ };
1597
+ }
1598
+ }
1599
+ function rendererValidationWarnings(validation, offlineIssues) {
1600
+ if (!validation.ran) {
1601
+ const base = `Plan content was NOT validated against the renderer schema (${validation.error ?? "validate endpoint unavailable"}). Fell back to the offline lint. Run against a Plan app that exposes ${VALIDATE_LOCAL_PLAN_ACTION} (e.g. --app-url http://localhost:8096) for authoritative validation.`;
1602
+ if (offlineIssues.length === 0)
1603
+ return [base];
1604
+ const preview = offlineIssues
1605
+ .slice(0, 8)
1606
+ .map((issue) => `${issue.file}:${issue.line} ${issue.message}`)
1607
+ .join("; ");
1608
+ const overflow = offlineIssues.length > 8 ? ` (+${offlineIssues.length - 8} more)` : "";
1609
+ return [base, `Offline lint rejected this plan: ${preview}${overflow}`];
1610
+ }
1611
+ if (validation.valid)
1612
+ return [];
1613
+ const issues = validation.issues ?? [];
1614
+ const preview = issues
1615
+ .slice(0, 8)
1616
+ .map((issue) => issue.path ? `${issue.path}: ${issue.message}` : issue.message)
1617
+ .join("; ");
1618
+ const overflow = issues.length > 8 ? ` (+${issues.length - 8} more)` : "";
1619
+ return [
1620
+ `Renderer schema rejected this plan — it will not render. ${preview}${overflow}`,
1621
+ ];
1622
+ }
1421
1623
  export async function verifyLocalPlanBridge(input) {
1422
1624
  const fetchFn = input.fetchFn ?? fetch;
1625
+ const files = readLocalPlanFiles(input.dir);
1423
1626
  const bridge = await startLocalPlanBridge({
1424
1627
  dir: input.dir,
1425
1628
  kind: input.kind,
@@ -1430,6 +1633,9 @@ export async function verifyLocalPlanBridge(input) {
1430
1633
  port: input.port,
1431
1634
  token: input.token,
1432
1635
  urlFile: input.urlFile,
1636
+ // Don't let the weaker offline lint abort the bridge before the authoritative
1637
+ // renderer check runs — verify reports the renderer's verdict, not the lint's.
1638
+ skipSourceValidation: true,
1433
1639
  });
1434
1640
  try {
1435
1641
  const preflight = await fetchFn(bridge.result.bridgeUrl, {
@@ -1456,8 +1662,21 @@ export async function verifyLocalPlanBridge(input) {
1456
1662
  const preflightOk = preflight.status === 204 &&
1457
1663
  preflight.headers.get("access-control-allow-origin") === "*" &&
1458
1664
  preflight.headers.get("access-control-allow-private-network") === "true";
1665
+ const validation = await fetchRendererValidation({
1666
+ appUrl: bridge.result.appUrl,
1667
+ files,
1668
+ fetchFn,
1669
+ });
1670
+ // The renderer's verdict is authoritative and gates `ok`. When it could not
1671
+ // run (old/unreachable Plan app), fall back to the offline lint as the
1672
+ // content signal so verify still catches the common cases offline instead
1673
+ // of silently passing unvalidated content.
1674
+ const offlineIssues = validation.ran ? [] : validateLocalPlanFiles(files);
1675
+ const contentOk = validation.ran
1676
+ ? validation.valid === true
1677
+ : offlineIssues.length === 0;
1459
1678
  return {
1460
- ok: bridgeOk && preflightOk,
1679
+ ok: bridgeOk && preflightOk && contentOk,
1461
1680
  dir: bridge.result.dir,
1462
1681
  url: bridge.result.url,
1463
1682
  ...(bridge.result.urlFile ? { urlFile: bridge.result.urlFile } : {}),
@@ -1482,10 +1701,14 @@ export async function verifyLocalPlanBridge(input) {
1482
1701
  ...(mdxFiles ? { mdxFiles } : {}),
1483
1702
  ...(payload?.error ? { error: payload.error } : {}),
1484
1703
  },
1485
- warnings: localPlanBridgeWarnings({
1486
- appUrl: bridge.result.appUrl,
1487
- bridgeUrl: bridge.result.bridgeUrl,
1488
- }),
1704
+ validation,
1705
+ warnings: [
1706
+ ...localPlanBridgeWarnings({
1707
+ appUrl: bridge.result.appUrl,
1708
+ bridgeUrl: bridge.result.bridgeUrl,
1709
+ }),
1710
+ ...rendererValidationWarnings(validation, offlineIssues),
1711
+ ],
1489
1712
  };
1490
1713
  }
1491
1714
  finally {
@@ -1551,7 +1774,9 @@ function runCheck(args) {
1551
1774
  const result = {
1552
1775
  ok: true,
1553
1776
  noDb: true,
1554
- validation: "passed",
1777
+ validation: "lint-passed",
1778
+ validationScope: "offline-lint",
1779
+ note: "Quick offline lint only — NOT the full Plan renderer schema. A green here does not guarantee the plan renders. Run `plan local verify` against a Plan app for authoritative validation before handing off.",
1555
1780
  dir: files.dir,
1556
1781
  title: parsed.frontmatter.title || firstHeading(parsed.body),
1557
1782
  kind: normalizeKind(parsed.frontmatter.kind),