Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@

What problem does this PR solve?


Why does this matter now?


What is the intended outcome?


What is intentionally out of scope?


What does success look like?


What should reviewers focus on?

<details>
Expand Down Expand Up @@ -75,13 +70,10 @@ Be mindful of private information like IP addresses, API keys, phone numbers, no

Which commands did you run?


What regression coverage was added or updated?


What failed before this fix, if known?


If no test was added, why not?

<details>
Expand All @@ -95,16 +87,12 @@ List focused commands, not every incidental check. CI is useful support, but ext

Did user-visible behavior change? (`Yes/No`)


Did config, environment, or migration behavior change? (`Yes/No`)


Did security, auth, secrets, network, or tool execution behavior change? (`Yes/No`)


What is the highest-risk area?


How is that risk mitigated?

<details>
Expand All @@ -118,10 +106,8 @@ Use this for author judgment that is not obvious from the diff. ClawSweeper can

What is the next action?


What is still waiting on author, maintainer, CI, or external proof?


Which bot or reviewer comments were addressed?

<details>
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,7 @@ jobs:
pnpm lint:auth:no-pairing-store-group
pnpm lint:auth:pairing-account-scope
pnpm check:import-cycles
pnpm format:check
# build-artifacts already runs the tsdown/runtime build for the same Node-relevant changes.
NODE_OPTIONS=--max-old-space-size=8192 pnpm build:plugin-sdk:strict-smoke
;;
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/full-release-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1139,7 +1139,16 @@ jobs:

summary:
name: Verify full validation
needs: [resolve_target, docker_runtime_assets_preflight, normal_ci, plugin_prerelease, release_checks, npm_telegram, performance]
needs:
[
resolve_target,
docker_runtime_assets_preflight,
normal_ci,
plugin_prerelease,
release_checks,
npm_telegram,
performance,
]
if: always()
runs-on: ubuntu-24.04
timeout-minutes: 5
Expand Down
5 changes: 1 addition & 4 deletions extensions/canvas/scripts/copy-a2ui.d.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
export declare function copyA2uiAssets(params: {
srcDir: string;
outDir: string;
}): Promise<void>;
export declare function copyA2uiAssets(params: { srcDir: string; outDir: string }): Promise<void>;
7 changes: 3 additions & 4 deletions extensions/diffs/src/viewer-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,9 @@ describe("createToolbarButton icon safety", () => {

it("SVG strings in toolbarIconSvg contain no XSS patterns", () => {
for (const pattern of XSS_PATTERNS) {
expect(
VIEWER_CLIENT_SRC.includes(pattern),
`source must not contain "${pattern}"`,
).toBe(false);
expect(VIEWER_CLIENT_SRC.includes(pattern), `source must not contain "${pattern}"`).toBe(
false,
);
}
});

Expand Down
64 changes: 48 additions & 16 deletions extensions/oc-path/src/oc-path/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ export function setMdOcPath(ast: MdAst, path: OcPath, newValue: string): MdEditR
guardSentinel(newValue, formatOcPath(path));
if (path.section === "[frontmatter]") {
const key = path.item ?? path.field;
if (key === undefined) {return { ok: false, reason: "unresolved" };}
if (key === undefined) {
return { ok: false, reason: "unresolved" };
}
const idx = ast.frontmatter.findIndex((e) => e.key === key);
if (idx === -1) {return { ok: false, reason: "unresolved" };}
if (idx === -1) {
return { ok: false, reason: "unresolved" };
}
const existing = ast.frontmatter[idx];
if (existing === undefined) {return { ok: false, reason: "unresolved" };}
if (existing === undefined) {
return { ok: false, reason: "unresolved" };
}
const newEntry: FrontmatterEntry = { ...existing, value: newValue };
const newFm = ast.frontmatter.slice();
newFm[idx] = newEntry;
Expand All @@ -43,16 +49,26 @@ export function setMdOcPath(ast: MdAst, path: OcPath, newValue: string): MdEditR

const sectionSlug = path.section.toLowerCase();
const blockIdx = ast.blocks.findIndex((b) => b.slug === sectionSlug);
if (blockIdx === -1) {return { ok: false, reason: "unresolved" };}
if (blockIdx === -1) {
return { ok: false, reason: "unresolved" };
}
const block = ast.blocks[blockIdx];
if (block === undefined) {return { ok: false, reason: "unresolved" };}
if (block === undefined) {
return { ok: false, reason: "unresolved" };
}

const itemSlug = path.item.toLowerCase();
const itemIdx = block.items.findIndex((i) => i.slug === itemSlug);
if (itemIdx === -1) {return { ok: false, reason: "unresolved" };}
if (itemIdx === -1) {
return { ok: false, reason: "unresolved" };
}
const item = block.items[itemIdx];
if (item === undefined) {return { ok: false, reason: "unresolved" };}
if (item.kv === undefined) {return { ok: false, reason: "no-item-kv" };}
if (item === undefined) {
return { ok: false, reason: "unresolved" };
}
if (item.kv === undefined) {
return { ok: false, reason: "no-item-kv" };
}
if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) {
return { ok: false, reason: "unresolved" };
}
Expand All @@ -78,9 +94,15 @@ function rebuildBlockBody(block: AstBlock, newItems: readonly AstItem[]): string
for (let i = 0; i < newItems.length; i++) {
const newItem = newItems[i];
const oldItem = block.items[i];
if (newItem === undefined || oldItem === undefined) {continue;}
if (newItem.kv === undefined || oldItem.kv === undefined) {continue;}
if (newItem.kv.value === oldItem.kv.value) {continue;}
if (newItem === undefined || oldItem === undefined) {
continue;
}
if (newItem.kv === undefined || oldItem.kv === undefined) {
continue;
}
if (newItem.kv.value === oldItem.kv.value) {
continue;
}
const re = new RegExp(`^(\\s*-\\s*${escapeRegex(oldItem.kv.key)}\\s*:\\s*).*$`, "m");
body = body.replace(re, `$1${newItem.kv.value}`);
}
Expand All @@ -101,19 +123,29 @@ function finalize(ast: MdAst): MdEditResult {
parts.push("---");
}
if (ast.preamble.length > 0) {
if (parts.length > 0) {parts.push("");}
if (parts.length > 0) {
parts.push("");
}
parts.push(ast.preamble);
}
for (const block of ast.blocks) {
if (parts.length > 0) {parts.push("");}
if (parts.length > 0) {
parts.push("");
}
parts.push(`## ${block.heading}`);
if (block.bodyText.length > 0) {parts.push(block.bodyText);}
if (block.bodyText.length > 0) {
parts.push(block.bodyText);
}
}
return { ok: true, ast: { ...ast, raw: parts.join("\n") } };
}

function formatFrontmatterValue(value: string): string {
if (value.length === 0) {return '""';}
if (/[:#&*?|<>=!%@`,[\]{}\r\n]/.test(value)) {return JSON.stringify(value);}
if (value.length === 0) {
return '""';
}
if (/[:#&*?|<>=!%@`,[\]{}\r\n]/.test(value)) {
return JSON.stringify(value);
}
return value;
}
4 changes: 3 additions & 1 deletion extensions/oc-path/src/oc-path/jsonc/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export function emitJsonc(ast: JsoncAst, opts: JsoncEmitOptions = {}): string {
}

// Render mode loses comments; walks leaves for caller-injected sentinel.
if (ast.root === null) {return "";}
if (ast.root === null) {
return "";
}
return renderValue(ast.root, guardPath, []);
}

Expand Down
12 changes: 9 additions & 3 deletions extensions/oc-path/src/oc-path/jsonc/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ export type JsoncOcPathMatch =
};

export function resolveJsoncOcPath(ast: JsoncAst, path: OcPath): JsoncOcPathMatch | null {
if (ast.root === null) {return null;}
if (ast.root === null) {
return null;
}

const segments: string[] = [];
const collect = (slot: string | undefined): void => {
if (slot === undefined) {return;}
if (slot === undefined) {
return;
}
for (const s of splitRespectingBrackets(slot, ".")) {
segments.push(isQuotedSeg(s) ? unquoteSeg(s) : s);
}
Expand All @@ -34,7 +38,9 @@ export function resolveJsoncOcPath(ast: JsoncAst, path: OcPath): JsoncOcPathMatc
collect(path.item);
collect(path.field);

if (segments.length === 0) {return { kind: "root", node: ast };}
if (segments.length === 0) {
return { kind: "root", node: ast };
}

return resolveJsoncValueOcPath(ast.root, segments);
}
17 changes: 5 additions & 12 deletions extensions/oc-path/src/oc-path/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@
*/

import MarkdownIt from "markdown-it";

import type {
AstBlock,
AstItem,
Diagnostic,
FrontmatterEntry,
ParseResult,
} from "./ast.js";
import type { AstBlock, AstItem, Diagnostic, FrontmatterEntry, ParseResult } from "./ast.js";
import { slugify } from "./slug.js";

type Token = ReturnType<MarkdownIt["parse"]>[number];
Expand Down Expand Up @@ -153,7 +146,9 @@ function extractItems(tokens: readonly Token[], bodyFileLine: number): AstItem[]
const items: AstItem[] = [];
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];
if (t.type !== "list_item_open" || t.map === null) {continue;}
if (t.type !== "list_item_open" || t.map === null) {
continue;
}
// First inline at the item's own depth is the item text.
let nestedDepth = 0;
let text = "";
Expand All @@ -175,9 +170,7 @@ function extractItems(tokens: readonly Token[], bodyFileLine: number): AstItem[]
text,
slug: kvMatch ? slugify(kvMatch[1]) : slugify(text),
line: bodyFileLine + t.map[0],
...(kvMatch !== null
? { kv: { key: kvMatch[1].trim(), value: kvMatch[2].trim() } }
: {}),
...(kvMatch !== null ? { kv: { key: kvMatch[1].trim(), value: kvMatch[2].trim() } } : {}),
});
}
return items;
Expand Down
44 changes: 33 additions & 11 deletions extensions/oc-path/src/oc-path/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,61 @@ export type OcPathMatch =
export function resolveMdOcPath(ast: MdAst, path: OcPath): OcPathMatch | null {
if (path.section === "[frontmatter]") {
const key = path.item ?? path.field;
if (key === undefined) {return null;}
if (key === undefined) {
return null;
}
const entry = ast.frontmatter.find((e) => e.key === key);
if (entry === undefined) {return null;}
if (entry === undefined) {
return null;
}
return { kind: "frontmatter", node: entry };
}

if (path.section === undefined) {return { kind: "root", node: ast };}
if (path.section === undefined) {
return { kind: "root", node: ast };
}

const block = ast.blocks.find((b) => b.slug === path.section!.toLowerCase());
if (block === undefined) {return null;}
if (path.item === undefined) {return { kind: "block", node: block };}
if (block === undefined) {
return null;
}
if (path.item === undefined) {
return { kind: "block", node: block };
}

// Item dispatch: ordinal (#N) > positional ($last) > slug.
// Ordinal uses document order so duplicate-slug items stay distinct.
let item: AstItem | undefined;
if (isOrdinalSeg(path.item)) {
const n = parseOrdinalSeg(path.item);
if (n === null || n < 0 || n >= block.items.length) {return null;}
if (n === null || n < 0 || n >= block.items.length) {
return null;
}
item = block.items[n];
} else if (isPositionalSeg(path.item)) {
const concrete = resolvePositionalSeg(path.item, {
indexable: true,
size: block.items.length,
});
if (concrete === null) {return null;}
if (concrete === null) {
return null;
}
item = block.items[Number(concrete)];
} else {
item = block.items.find((i) => i.slug === path.item!.toLowerCase());
}
if (item === undefined) {return null;}
if (path.field === undefined) {return { kind: "item", node: item, block };}
if (item === undefined) {
return null;
}
if (path.field === undefined) {
return { kind: "item", node: item, block };
}

if (item.kv === undefined) {return null;}
if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) {return null;}
if (item.kv === undefined) {
return null;
}
if (item.kv.key.toLowerCase() !== path.field.toLowerCase()) {
return null;
}
return { kind: "item-field", node: item, block, value: item.kv.value };
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1939,7 +1939,7 @@
"jscpd": "4.2.4",
"jsdom": "29.1.1",
"lit": "3.3.3",
"oxfmt": "0.52.0",
"oxfmt": "0.65.0",
"oxlint": "1.67.0",
"oxlint-tsgolint": "0.23.0",
"shiki": "4.1.0",
Expand Down
Loading
Loading