Skip to content
Merged
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
102 changes: 102 additions & 0 deletions .config/oxlint/brunch/base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"$schema": "../../../node_modules/oxlint/configuration_schema.json",
"plugins": [
"eslint",
"typescript",
"unicorn",
"oxc",
"import",
"jsdoc",
"node",
"promise",
"vitest"
],
"rules": {
"array-callback-return": ["error", { "allowImplicit": true }],
"default-case-last": "error",
"default-param-last": "error",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"guard-for-in": "error",
"no-alert": "error",
"no-bitwise": "error",
"no-cond-assign": ["error", "always"],
"no-console": "error",
"no-extend-native": "error",
"no-loop-func": "error",
"no-multi-assign": "error",
"no-new": "error",
"no-new-func": "error",
"no-param-reassign": [
"error",
{
"props": true,
"ignorePropertyModificationsForRegex": ["^existing", "draft"]
}
],
"no-return-assign": ["error", "always"],
"no-self-compare": "error",
"no-sequences": "error",
"no-template-curly-in-string": "error",
"no-unsafe-optional-chaining": [
"error",
{ "disallowArithmeticOperators": true }
],
"no-unused-vars": [
"error",
{
"args": "all",
"argsIgnorePattern": "^_+",
"varsIgnorePattern": "^_+"
}
],
"no-void": ["error", { "allowAsStatement": true }],
"func-names": "error",
"new-cap": "error",
"import/no-cycle": "error",
"import/no-duplicates": "error",
"import/no-mutable-exports": "error",
"import/no-named-as-default": "error",
"import/no-named-as-default-member": "error",
"import/no-named-default": "error",
"import/no-self-import": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-expect-error": "allow-with-description",
"minimumDescriptionLength": 10
}
],
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unnecessary-type-constraint": "error",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-return": "error",
"unicorn/filename-case": "error",
"unicorn/no-new-array": "off",
"vitest/valid-expect": "off",
"constructor-super": "off",
"no-class-assign": "off",
"no-const-assign": "off",
"no-constant-condition": "off",
"no-dupe-keys": "off",
"no-func-assign": "off",
"no-import-assign": "off",
"no-obj-calls": "off",
"no-redeclare": "off",
"no-setter-return": "off",
"no-this-before-super": "off",
"no-throw-literal": "off",
"no-unsafe-negation": "off",
"prefer-promise-reject-errors": "off"
}
}
49 changes: 49 additions & 0 deletions .config/oxlint/brunch/react.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"$schema": "../../../node_modules/oxlint/configuration_schema.json",
"extends": ["./base.json"],
"plugins": [
"eslint",
"typescript",
"unicorn",
"oxc",
"import",
"jsdoc",
"node",
"promise",
"vitest",
"react",
"react-perf",
"jsx-a11y"
],
"rules": {
"react/button-has-type": [
"error",
{ "button": true, "submit": true, "reset": false }
],
"react/jsx-no-comment-textnodes": "error",
"react/jsx-no-target-blank": ["error", { "enforceDynamicLinks": "always" }],
"react/jsx-pascal-case": ["error", { "allowAllCaps": true }],
"react/no-array-index-key": "error",
"react/no-danger": "error",
"jsx-a11y/aria-role": ["error", { "ignoreNonDOM": false }],
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/no-noninteractive-tabindex": [
"error",
{ "tags": [], "roles": ["tabpanel"] }
],
"jsx-a11y/no-static-element-interactions": [
"error",
{
"handlers": [
"onClick",
"onMouseDown",
"onMouseUp",
"onKeyPress",
"onKeyDown",
"onKeyUp"
]
}
],
"jsx-a11y/prefer-tag-over-role": "off"
}
}
1 change: 1 addition & 0 deletions .github/actions/prune-repository/prune.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
# architecture tests in packages/core read its docs, scripts, and agent
# contract files
"@hashintel/brunch-agent": [
".config/oxlint/brunch",
"libs/@hashintel/brunch-agent/AGENTS.md",
"libs/@hashintel/brunch-agent/CONTEXT.md",
"libs/@hashintel/brunch-agent/docs",
Expand Down
61 changes: 61 additions & 0 deletions apps/brunch-agent/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"extends": ["../../.config/oxlint/brunch/react.json"],
"categories": {
"correctness": "error",
"perf": "warn"
},
"options": {
"typeAware": true,
"typeCheck": true
},
"env": {
"builtin": true,
"es2026": true,
"node": true
},
"settings": {
"react": {
"version": "19.2"
}
},
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "@hashintel/petrinaut",
"message": "The Brunch server must remain independent of Petrinaut implementations."
}
],
"patterns": [
{
"group": ["@local/*"],
"message": "The Brunch application must not depend on unpublished HASH packages."
},
{
"group": ["@hashintel/petrinaut/*", "@hashintel/petrinaut-*"],
"message": "The Brunch server must remain independent of Petrinaut implementations."
}
]
}
]
},
"overrides": [
{
"files": ["src/ui/**/*.{ts,tsx}"],
"env": {
"browser": true
}
}
],
"ignorePatterns": [
"dist/**",
"build/**",
"coverage/**",
"*.gen.*",
"*.tsbuildinfo",
".turbo/**"
]
}
5 changes: 3 additions & 2 deletions apps/brunch-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"scripts": {
"build": "vite build && vite build --config vite.client.config.ts",
"dev": "vite dev",
"fix:eslint": "oxlint --fix --type-aware --report-unused-disable-directives-severity=error .",
"lint:eslint": "oxlint --type-aware --report-unused-disable-directives-severity=error .",
"fix:eslint": "oxlint --fix --type-aware --type-check --report-unused-disable-directives-severity=error .",
"lint:eslint": "oxlint --type-aware --type-check --report-unused-disable-directives-severity=error .",
"lint:tsc": "tsgo --noEmit",
"petrinaut:dev": "vite dev --config petrinaut-local.vite.config.ts",
"test:unit": "vitest run --config vitest.config.ts"
Expand All @@ -34,6 +34,7 @@
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260511.1",
"ai": "6.0.182",
"oxlint": "1.63.0",
"oxlint-tsgolint": "0.22.1",
"vite": "8.1.0",
Expand Down
11 changes: 7 additions & 4 deletions apps/brunch-agent/src/agents/gherkin-elicitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ import { createGherkinElicitationSession } from "../elicitation-session.ts";
*/
export const GHERKIN_MODEL_ID = "claude-haiku-4-5";

const gherkinElicitorInitialData = v.object({
targetDocumentId: v.pipe(v.string(), v.nonEmpty()),
});

export function GherkinElicitor(props: AgentProps) {
useModel(`anthropic/${GHERKIN_MODEL_ID}`);
const initialData = useInitialData<{ targetDocumentId: string }>();
const initialData =
useInitialData<v.InferOutput<typeof gherkinElicitorInitialData>>();
return useElicitation(
gherkin,
createGherkinElicitationSession(props.id, initialData.targetDocumentId),
Expand Down Expand Up @@ -68,6 +73,4 @@ GherkinElicitor.agentName = "brunch-gherkin-elicitor";
* to an existing conversation id resumes that session against the current state
* of its target-document.
*/
GherkinElicitor.initialData = v.object({
targetDocumentId: v.pipe(v.string(), v.nonEmpty()),
});
GherkinElicitor.initialData = gherkinElicitorInitialData;
1 change: 1 addition & 0 deletions apps/brunch-agent/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ app.on(["POST", "OPTIONS"], PETRINAUT_CHAT_ROUTE, (c) =>
// client build is a second, plain vite build β€” without it the ui tree would
// have no build coverage at all.
const uiRoot = new URL(
// oxlint-disable-next-line typescript/no-unnecessary-condition -- import.meta.env is absent when Node executes this module directly.
import.meta.env?.DEV === false ? "./client/" : "../",
import.meta.url,
);
Expand Down
8 changes: 6 additions & 2 deletions apps/brunch-agent/src/elicitation-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import {
createFlueHistoryReader,
createLocalCaptureStore,
type ElicitationSession,
type FlueHistoryReaderOptions,
} from "@hashintel/brunch-agent-binding-flue";

import { GHERKIN_AGENT_ROUTE } from "./routes.ts";
import { targetDocumentPath } from "./target-document-path.ts";

const appTransport = (async (input: RequestInfo | URL, init?: RequestInit) => {
const appTransport: FlueHistoryReaderOptions["transport"] = async (
input,
init,
) => {
const { default: app } = await import("./app.ts");
return app.fetch(input instanceof Request ? input : new Request(input, init));
}) as typeof fetch;
};

export const createGherkinElicitationSession = (
sessionId: string,
Expand Down
2 changes: 1 addition & 1 deletion apps/brunch-agent/src/petrinaut-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const inspect =
? (event: TransportInspectionEvent): void => {
// This is an opt-in shell diagnostic stream. It is never dispatched
// into Flue and therefore cannot become elicitation evidence.
console.log(`TRANSPORT_AISDK ${JSON.stringify(event)}`);
process.stdout.write(`TRANSPORT_AISDK ${JSON.stringify(event)}\n`);
}
: undefined;

Expand Down
1 change: 1 addition & 0 deletions apps/brunch-agent/src/ui/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function VisibleMessage({ message }: { message: FlueConversationMessage }) {
{message.parts.map((part, index) => {
if (part.type === "text") {
return (
// oxlint-disable-next-line react/no-array-index-key -- Flue text parts expose no stable identifier.
<p className="message__text" key={index}>
{part.text}
</p>
Expand Down
19 changes: 19 additions & 0 deletions apps/brunch-agent/test/petrinaut-ask-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { UIMessageChunk } from "ai";

type ToolInputChunk = Extract<UIMessageChunk, { type: "tool-input-available" }>;
type ToolOutputChunk = Extract<
UIMessageChunk,
{ type: "tool-output-available" }
>;

export interface PetrinautAskResult {
readonly initialStatus: number;
readonly askCall: ToolInputChunk | undefined;
readonly initialToolOutputs: readonly ToolOutputChunk[];
readonly initialFinish: UIMessageChunk | undefined;
readonly resumedStatus: number;
readonly resumedText: string;
readonly resumedFinish: UIMessageChunk | undefined;
readonly duplicateStatus: number;
readonly duplicateBody: unknown;
}
Loading
Loading