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
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ inputs:
description: Token used to upsert a pull request comment. If neither this input nor GITHUB_TOKEN is set, FixMap only writes the step summary.
required: false
comment-author:
description: GitHub login that owns prior FixMap comments. Defaults to github-actions[bot].
description: Optional GitHub login used to disambiguate prior FixMap comments when multiple marked comments exist.
required: false
outputs:
report:
Expand Down
90 changes: 51 additions & 39 deletions apps/web/app/demo.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
"use client";

import { useMemo, useState } from "react";
import { buildTestRoutes, rankContextFiles, type RepoFile, type RepoMap } from "@aryam/fixmap-core/browser";

const files = [
{ path: "src/auth/reset-password.ts", text: "password reset token email authentication", kind: "code" },
{ path: "src/auth/session.ts", text: "login session cookie authentication", kind: "code" },
{ path: "src/billing/create-invoice.ts", text: "billing payment invoice customer", kind: "code" },
{ path: "src/email/send-reset.ts", text: "send password reset email template", kind: "code" },
{ path: "test/auth/reset-password.test.ts", text: "password reset email token test", kind: "test" },
{ path: ".github/workflows/ci.yml", text: "workflow test build pull request", kind: "config" },
{ path: "README.md", text: "installation guide documentation", kind: "documentation" }
] as const;
const sampleFiles = [
sampleFile("src/auth/reset-password.ts", "password reset token email authentication", "code"),
sampleFile("src/auth/session.ts", "login session cookie authentication", "code"),
sampleFile("src/billing/create-invoice.ts", "billing payment invoice customer", "code"),
sampleFile("src/email/send-reset.ts", "send password reset email template", "code"),
sampleFile("test/auth/reset-password.test.ts", "password reset email token test", "code", true),
sampleFile(".github/workflows/ci.yml", "workflow test build pull request", "config"),
sampleFile("README.md", "installation guide documentation", "documentation")
];

const sampleRepo: RepoMap = {
root: "sample-repository",
files: sampleFiles,
packageScripts: [{ name: "test", command: "vitest run", packageDir: "" }],
changedFiles: [],
diffText: "",
packageManager: "npm",
diagnostics: []
};

const presets = [
"Password reset emails fail",
Expand All @@ -19,32 +30,12 @@ const presets = [
"Update the installation guide"
];

function tokens(value: string) {
return new Set(value.toLowerCase().split(/[^a-z0-9]+/).map((token) => {
if (token.length > 5 && token.endsWith("ies")) return `${token.slice(0, -3)}y`;
if (token.length > 4 && token.endsWith("ed")) return token.slice(0, -1);
if (token.length > 4 && token.endsWith("es")) return token.slice(0, -1);
if (token.length > 3 && token.endsWith("s")) return token.slice(0, -1);
return token;
}).filter((token) => token.length > 2));
}

export function Demo() {
const [task, setTask] = useState<string>("Password reset emails fail");
const ranked = useMemo(() => {
const taskTokens = tokens(task);
return files
.map((file) => {
const pathTokens = tokens(file.path);
const contentTokens = tokens(file.text);
const pathMatches = [...pathTokens].filter((token) => taskTokens.has(token));
const contentMatches = [...contentTokens].filter((token) => taskTokens.has(token));
const score = pathMatches.length * 3 + contentMatches.length * 2 + (file.kind === "code" ? 2 : 0);
return { ...file, score, matches: [...new Set([...pathMatches, ...contentMatches])] };
})
.filter((file) => file.score > 2)
.sort((a, b) => b.score - a.score)
.slice(0, 4);
const result = useMemo(() => {
const ranked = rankContextFiles(sampleRepo, { issueText: task }, 4);
const routes = buildTestRoutes(sampleRepo, ranked.map((file) => file.path));
return { ranked, routes };
}, [task]);

return (
Expand All @@ -57,19 +48,40 @@ export function Demo() {
<button key={preset} type="button" aria-pressed={task === preset} onClick={() => setTask(preset)}>{preset}</button>
))}
</div>
<div className="privacy-note"><span></span><p><strong>Sample data only.</strong> Nothing typed here leaves your browser.</p></div>
<div className="privacy-note"><span></span><p><strong>Sample data only.</strong> Nothing typed here leaves your browser.</p></div>
</div>
<div className="demo-results" aria-live="polite">
<div className="results-head"><span>Context pack</span><small>{ranked.length} files</small></div>
{ranked.length ? ranked.map((file, index) => (
<div className="results-head"><span>Context pack</span><small>{result.ranked.length} files</small></div>
{result.ranked.length ? result.ranked.map((file, index) => (
<article className="result" key={file.path}>
<span className="result-number">{String(index + 1).padStart(2, "0")}</span>
<div><code>{file.path}</code><p>{file.matches.length ? `Matches: ${file.matches.join(", ")}` : "Repository structure signal"}</p></div>
<span className={`confidence ${file.score >= 10 ? "high" : file.score >= 6 ? "medium" : "low"}`}>{file.score >= 10 ? "high" : file.score >= 6 ? "medium" : "low"}</span>
<div><code>{file.path}</code><p>{file.reasons.join("; ")}</p></div>
<span className={`confidence ${file.confidence}`}>{file.confidence}</span>
</article>
)) : <div className="empty-result"><strong>No confident match yet.</strong><p>Try mentioning a feature, file, or behavior.</p></div>}
<div className="route-preview"><span>Suggested check</span><code>{task.toLowerCase().includes("guide") ? "No code test required" : "npm run test"}</code></div>
<div className="route-preview">
<span>Suggested check</span>
<code>{result.routes[0]?.command ?? "No code test required"}</code>
</div>
</div>
</div>
);
}

function sampleFile(
path: string,
textSample: string,
kind: RepoFile["kind"],
isTest = false
): RepoFile {
const extensionIndex = path.lastIndexOf(".");
return {
path,
extension: extensionIndex >= 0 ? path.slice(extensionIndex) : "",
sizeBytes: textSample.length,
isTest,
isSource: true,
kind,
textSample
};
}
3 changes: 2 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"overrides": {
"next": {
"postcss": "8.5.16"
"postcss": "8.5.23"
}
},
"scripts": {
Expand All @@ -15,6 +15,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@aryam/fixmap-core": "0.7.0",
"next": "16.2.11",
"react": "19.2.7",
"react-dom": "19.2.7"
Expand Down
12 changes: 12 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import tseslint from "typescript-eslint";

export default tseslint.config(
{
ignores: [
"**/dist/**",
"**/.next/**",
"**/node_modules/**"
]
},
...tseslint.configs.recommended
);
2 changes: 1 addition & 1 deletion examples/reports/workspace-api-discount.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FixMap found 3 context files and generated 3 test routes.
## Context Files

- `apps/api/src/orders.ts` (high confidence, score 17): path matches task terms: order; content matches task terms: discount, order, total, code; defines task identifiers: orderTotal
- `README.md` (medium confidence, score 8): content matches task terms: order, total, ignore, unknown, discount, code, value
- `README.md` (medium confidence, score 8): content matches task terms: order, total, ignor, unknown, discount, code, valu
- `packages/utils/src/currency.ts` (low confidence, score 6): content matches task terms: discount, total

## Test Route
Expand Down
2 changes: 1 addition & 1 deletion examples/reports/workspace-utils-rounding.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FixMap found 2 context files and generated 3 test routes.
## Context Files

- `packages/utils/src/currency.ts` (medium confidence, score 13): path matches task terms: currency; content matches task terms: round, cent; defines task identifiers: roundToCents
- `README.md` (low confidence, score 6): content matches task terms: round, cent, keep, fraction, formatt, currency
- `README.md` (low confidence, score 6): content matches task terms: round, cent, keep, fraction, format, currency

## Test Route

Expand Down
19 changes: 11 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,19 @@
},
"overrides": {
"next@16.2.11": {
"postcss": "8.5.16"
"postcss": "8.5.23"
},
"fast-uri": "3.1.4",
"sharp": "0.35.3"
},
"devDependencies": {
"@types/node": "^22.0.0",
"eslint": "9.39.4",
"esbuild": "^0.28.1",
"next": "16.2.11",
"postcss": "8.5.16",
"postcss": "8.5.23",
"typescript": "^6.0.3",
"typescript-eslint": "8.63.0",
"vitest": "^4.1.9"
}
}
2 changes: 1 addition & 1 deletion packages/action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ inputs:
description: Token used to upsert a pull request comment. If neither this input nor GITHUB_TOKEN is set, FixMap only writes the step summary.
required: false
comment-author:
description: GitHub login that owns prior FixMap comments. Defaults to github-actions[bot].
description: Optional GitHub login used to disambiguate prior FixMap comments when multiple marked comments exist.
required: false
outputs:
report:
Expand Down
Loading