@ai-agent-forge/plugin-formatter-linter 0.85.0
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.
- package/README.md +40 -0
- package/agent-forge.json +12 -0
- package/dist/capability.d.ts +5 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +238 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +12 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +13 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/node-adapter.d.ts +17 -0
- package/dist/node-adapter.d.ts.map +1 -0
- package/dist/node-adapter.js +381 -0
- package/dist/node-adapter.js.map +1 -0
- package/dist/sanitize.d.ts +15 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +43 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/plugin.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @agent-forge/plugin-formatter-linter
|
|
2
|
+
|
|
3
|
+
Formatter-linter capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), extracted from the
|
|
4
|
+
host's `src/plugins/formatter-linter` (D-075 S2 pilot). It registers a single read-only tool, `formatter_linter_check`,
|
|
5
|
+
which asks the host to run an approved lint or formatting check and returns bounded, path-free diagnostics.
|
|
6
|
+
|
|
7
|
+
The plugin deliberately has no execution engine of its own. It never discovers or invokes a local formatter/linter; the
|
|
8
|
+
host resolves an opaque execution plan and runs an approved runner. Without a host-bound runner the tool reports an
|
|
9
|
+
explicit `unsupported` result.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
# from a local checkout
|
|
15
|
+
agent-forge plugin install ./packages/plugin-formatter-linter
|
|
16
|
+
|
|
17
|
+
# from npm
|
|
18
|
+
agent-forge plugin install @agent-forge/plugin-formatter-linter
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The package ships two manifests: `agent-forge.json` (ecosystem manifest for the deterministic plugin resolver) and
|
|
22
|
+
`plugin.json` (host-load manifest consumed by `loadCapabilityPluginFromManifest`, entry `./dist/entry.js`).
|
|
23
|
+
|
|
24
|
+
## Library surface
|
|
25
|
+
|
|
26
|
+
`import { createNodeFormatterLinterAdapterV1, createFormatterLinterCapabilityFactoryV1 } from "@agent-forge/plugin-formatter-linter";`
|
|
27
|
+
|
|
28
|
+
- `createNodeFormatterLinterAdapterV1(options?)` — read-only adapter; requires host-injected `resolver`/`runner` to
|
|
29
|
+
produce results, otherwise returns `unsupported`.
|
|
30
|
+
- `createDeterministicFormatterLinterAdapterV1(fixtures)` — deterministic fixture adapter for tests.
|
|
31
|
+
- `createFormatterLinterCapabilityFactoryV1(adapter)` — registers `formatter_linter_check` on a `CapabilityAPI`.
|
|
32
|
+
- Default export of `dist/entry.js` — the precomposed factory used by external plugin loading.
|
|
33
|
+
|
|
34
|
+
## Development
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install --ignore-scripts # repo root, creates the workspace symlink
|
|
38
|
+
npm run build # packages/plugin-formatter-linter (tsgo)
|
|
39
|
+
npm test # vitest; the entry fidelity test requires `npm run build` first
|
|
40
|
+
```
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.formatter-linter",
|
|
4
|
+
"pluginVersion": "0.84.4",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [{ "id": "formatter-linter.check", "version": 1 }],
|
|
8
|
+
"platform": {
|
|
9
|
+
"os": ["windows", "macos", "linux"],
|
|
10
|
+
"runtime": [{ "name": "node" }]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
2
|
+
import type { FormatterLinterAdapterV1 } from "./types.ts";
|
|
3
|
+
/** Registers only a read-only report tool. It never offers a formatting write action. */
|
|
4
|
+
export declare function createFormatterLinterCapabilityFactoryV1(runtime: FormatterLinterAdapterV1): CapabilityPluginSyncFactory;
|
|
5
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAS3E,OAAO,KAAK,EAAE,wBAAwB,EAA2B,MAAM,YAAY,CAAC;AAkNpF,yFAAyF;AACzF,wBAAgB,wCAAwC,CACvD,OAAO,EAAE,wBAAwB,GAC/B,2BAA2B,CAwD7B","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tFORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1,\n\tprojectFormatterLinterResultV1,\n\tsanitizeFormatterLinterTextV1,\n} from \"./node-adapter.ts\";\nimport type { FormatterLinterAdapterV1, FormatterLinterResultV1 } from \"./types.ts\";\n\nconst opaqueRef = Type.String({\n\tminLength: 1,\n\tmaxLength: FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n});\nconst requestSchema = Type.Object(\n\t{\n\t\tworkspaceRef: opaqueRef,\n\t\tlanguage: Type.Union([\n\t\t\tType.Literal(\"typescript\"),\n\t\t\tType.Literal(\"javascript\"),\n\t\t\tType.Literal(\"json\"),\n\t\t\tType.Literal(\"css\"),\n\t\t\tType.Literal(\"html\"),\n\t\t\tType.Literal(\"markdown\"),\n\t\t\tType.Literal(\"yaml\"),\n\t\t\tType.Literal(\"python\"),\n\t\t\tType.Literal(\"go\"),\n\t\t\tType.Literal(\"rust\"),\n\t\t\tType.Literal(\"java\"),\n\t\t\tType.Literal(\"c\"),\n\t\t\tType.Literal(\"cpp\"),\n\t\t\tType.Literal(\"csharp\"),\n\t\t\tType.Literal(\"ruby\"),\n\t\t\tType.Literal(\"php\"),\n\t\t]),\n\t\ttool: Type.Union([\n\t\t\tType.Literal(\"eslint\"),\n\t\t\tType.Literal(\"prettier\"),\n\t\t\tType.Literal(\"biome\"),\n\t\t\tType.Literal(\"ruff\"),\n\t\t\tType.Literal(\"gofmt\"),\n\t\t\tType.Literal(\"black\"),\n\t\t\tType.Literal(\"clang-format\"),\n\t\t\tType.Literal(\"golangci-lint\"),\n\t\t]),\n\t\toperation: Type.Union([Type.Literal(\"lint\"), Type.Literal(\"format-check\")]),\n\t\ttargetRef: opaqueRef,\n\t\ttimeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),\n\t\tmaxDiagnostics: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Formatter-linter input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\tconst candidate = typeof first === \"string\" && first.trim() !== \"\" ? first : \"profile-runtime-tool-call\";\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId:\n\t\t\tcandidate.length <= FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1 ? candidate : \"profile-runtime-tool-call\",\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction emptySummary(): FormatterLinterResultV1[\"summary\"] {\n\treturn { targetsChecked: 0, total: 0, errors: 0, warnings: 0, infos: 0, formatIssues: 0 };\n}\n\nfunction adapterFailed(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Formatter-linter adapter returned an invalid result\" },\n\t};\n}\n\nfunction invalidInput(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Formatter-linter request is invalid\" },\n\t};\n}\n\nfunction cancelled(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Formatter-linter check was cancelled\" },\n\t};\n}\n\nfunction validSummary(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst summary = value as Record<string, unknown>;\n\tconst keys = [\"targetsChecked\", \"total\", \"errors\", \"warnings\", \"infos\", \"formatIssues\"];\n\tif (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary))) return false;\n\tconst counts = keys.map((key) => summary[key]);\n\tif (counts.some((count) => !Number.isSafeInteger(count) || (count as number) < 0 || (count as number) > 10_000_000))\n\t\treturn false;\n\treturn (\n\t\t(counts[2] as number) + (counts[3] as number) + (counts[4] as number) === counts[1] &&\n\t\t(counts[5] as number) <= counts[1]\n\t);\n}\n\nfunction validError(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst error = value as Record<string, unknown>;\n\tif (Object.keys(error).some((key) => ![\"version\", \"code\", \"message\", \"causeCode\"].includes(key))) return false;\n\treturn (\n\t\terror.version === 1 &&\n\t\ttypeof error.code === \"string\" &&\n\t\t/^[a-z][a-z0-9_]{0,127}$/u.test(error.code) &&\n\t\ttypeof error.message === \"string\" &&\n\t\terror.message.trim() !== \"\" &&\n\t\terror.message.length <= 512 &&\n\t\t(error.causeCode === undefined ||\n\t\t\t(typeof error.causeCode === \"string\" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode)))\n\t);\n}\n\nfunction boundedResult(value: unknown, operationId: string): FormatterLinterResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return adapterFailed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tif (result.status === \"succeeded\")\n\t\treturn projectFormatterLinterResultV1(result, operationId) ?? adapterFailed(operationId);\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\t!([\"failed\", \"cancelled\", \"needs_user\", \"unsupported\"] as readonly string[]).includes(result.status as string) ||\n\t\t!Array.isArray(result.diagnostics) ||\n\t\tresult.diagnostics.length !== 0 ||\n\t\tresult.diagnosticsTruncated !== false ||\n\t\tresult.error === undefined ||\n\t\t(result.reportArtifactRef !== undefined &&\n\t\t\t(typeof result.reportArtifactRef !== \"string\" ||\n\t\t\t\t!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(result.reportArtifactRef))) ||\n\t\tObject.keys(result).some(\n\t\t\t(key) =>\n\t\t\t\t![\n\t\t\t\t\t\"version\",\n\t\t\t\t\t\"operationId\",\n\t\t\t\t\t\"status\",\n\t\t\t\t\t\"summary\",\n\t\t\t\t\t\"diagnostics\",\n\t\t\t\t\t\"diagnosticsTruncated\",\n\t\t\t\t\t\"reportArtifactRef\",\n\t\t\t\t\t\"error\",\n\t\t\t\t].includes(key),\n\t\t)\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (!validSummary(result.summary) || !validError(result.error)) return adapterFailed(operationId);\n\tconst error = result.error as {\n\t\treadonly version: 1;\n\t\treadonly code: string;\n\t\treadonly message: string;\n\t\treadonly causeCode?: string;\n\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: result.status as FormatterLinterResultV1[\"status\"],\n\t\tsummary: result.summary as FormatterLinterResultV1[\"summary\"],\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\t...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef as string }),\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeFormatterLinterTextV1(error.message),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t},\n\t};\n}\n\nconst CANCELLATION_V1 = Symbol(\"formatter-linter-capability-cancelled\");\n\nfunction runWithCancellation(\n\trun: () => Promise<FormatterLinterResultV1>,\n\tsignal: AbortSignal | undefined,\n): Promise<FormatterLinterResultV1 | typeof CANCELLATION_V1> {\n\tif (signal === undefined) return run();\n\tlet onAbort!: () => void;\n\tconst cancellation = new Promise<typeof CANCELLATION_V1>((resolve) => {\n\t\tonAbort = () => resolve(CANCELLATION_V1);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n\tconst pending = run();\n\treturn Promise.race([pending, cancellation]).finally(() => signal.removeEventListener(\"abort\", onAbort));\n}\n\n/** Registers only a read-only report tool. It never offers a formatting write action. */\nexport function createFormatterLinterCapabilityFactoryV1(\n\truntime: FormatterLinterAdapterV1,\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tapi.registerTool({\n\t\t\tname: \"formatter_linter_check\",\n\t\t\tlabel: \"Check formatting and lint\",\n\t\t\tdescription: \"Run a host-approved lint or formatting check and return bounded diagnostics\",\n\t\t\tparameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(\"profile-runtime-tool-call\");\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet requestInput: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tif (!Value.Check(Type.Object({ request: requestSchema }, { additionalProperties: false }), input))\n\t\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t\trequestInput = objectInput(input.request, undefined);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\truntime.run(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\t\tworkspaceRef: requestInput.workspaceRef as string,\n\t\t\t\t\t\t\t\t\tlanguage: requestInput.language as never,\n\t\t\t\t\t\t\t\t\ttool: requestInput.tool as never,\n\t\t\t\t\t\t\t\t\toperation: requestInput.operation as never,\n\t\t\t\t\t\t\t\t\ttargetRef: requestInput.targetRef as string,\n\t\t\t\t\t\t\t\t\t...(requestInput.timeoutMs === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { timeoutMs: requestInput.timeoutMs as number }),\n\t\t\t\t\t\t\t\t\t...(requestInput.maxDiagnostics === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { maxDiagnostics: requestInput.maxDiagnostics as number }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t);\n\t\t\t\t\tif (output === CANCELLATION_V1) return cancelled(operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
import { FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1, FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1, projectFormatterLinterResultV1, sanitizeFormatterLinterTextV1, } from "./node-adapter.js";
|
|
4
|
+
const opaqueRef = Type.String({
|
|
5
|
+
minLength: 1,
|
|
6
|
+
maxLength: FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,
|
|
7
|
+
pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$",
|
|
8
|
+
});
|
|
9
|
+
const requestSchema = Type.Object({
|
|
10
|
+
workspaceRef: opaqueRef,
|
|
11
|
+
language: Type.Union([
|
|
12
|
+
Type.Literal("typescript"),
|
|
13
|
+
Type.Literal("javascript"),
|
|
14
|
+
Type.Literal("json"),
|
|
15
|
+
Type.Literal("css"),
|
|
16
|
+
Type.Literal("html"),
|
|
17
|
+
Type.Literal("markdown"),
|
|
18
|
+
Type.Literal("yaml"),
|
|
19
|
+
Type.Literal("python"),
|
|
20
|
+
Type.Literal("go"),
|
|
21
|
+
Type.Literal("rust"),
|
|
22
|
+
Type.Literal("java"),
|
|
23
|
+
Type.Literal("c"),
|
|
24
|
+
Type.Literal("cpp"),
|
|
25
|
+
Type.Literal("csharp"),
|
|
26
|
+
Type.Literal("ruby"),
|
|
27
|
+
Type.Literal("php"),
|
|
28
|
+
]),
|
|
29
|
+
tool: Type.Union([
|
|
30
|
+
Type.Literal("eslint"),
|
|
31
|
+
Type.Literal("prettier"),
|
|
32
|
+
Type.Literal("biome"),
|
|
33
|
+
Type.Literal("ruff"),
|
|
34
|
+
Type.Literal("gofmt"),
|
|
35
|
+
Type.Literal("black"),
|
|
36
|
+
Type.Literal("clang-format"),
|
|
37
|
+
Type.Literal("golangci-lint"),
|
|
38
|
+
]),
|
|
39
|
+
operation: Type.Union([Type.Literal("lint"), Type.Literal("format-check")]),
|
|
40
|
+
targetRef: opaqueRef,
|
|
41
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),
|
|
42
|
+
maxDiagnostics: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),
|
|
43
|
+
}, { additionalProperties: false });
|
|
44
|
+
function objectInput(first, second) {
|
|
45
|
+
const value = second === undefined ? first : second;
|
|
46
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
47
|
+
throw new Error("Formatter-linter input must be an object");
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function invocation(first, second) {
|
|
51
|
+
const candidate = typeof first === "string" && first.trim() !== "" ? first : "profile-runtime-tool-call";
|
|
52
|
+
return {
|
|
53
|
+
input: objectInput(first, second),
|
|
54
|
+
operationId: candidate.length <= FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1 ? candidate : "profile-runtime-tool-call",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function signalFrom(value) {
|
|
58
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
59
|
+
}
|
|
60
|
+
function emptySummary() {
|
|
61
|
+
return { targetsChecked: 0, total: 0, errors: 0, warnings: 0, infos: 0, formatIssues: 0 };
|
|
62
|
+
}
|
|
63
|
+
function adapterFailed(operationId) {
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
operationId,
|
|
67
|
+
status: "failed",
|
|
68
|
+
summary: emptySummary(),
|
|
69
|
+
diagnostics: [],
|
|
70
|
+
diagnosticsTruncated: false,
|
|
71
|
+
error: { version: 1, code: "adapter_failed", message: "Formatter-linter adapter returned an invalid result" },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function invalidInput(operationId) {
|
|
75
|
+
return {
|
|
76
|
+
version: 1,
|
|
77
|
+
operationId,
|
|
78
|
+
status: "failed",
|
|
79
|
+
summary: emptySummary(),
|
|
80
|
+
diagnostics: [],
|
|
81
|
+
diagnosticsTruncated: false,
|
|
82
|
+
error: { version: 1, code: "invalid_input", message: "Formatter-linter request is invalid" },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function cancelled(operationId) {
|
|
86
|
+
return {
|
|
87
|
+
version: 1,
|
|
88
|
+
operationId,
|
|
89
|
+
status: "cancelled",
|
|
90
|
+
summary: emptySummary(),
|
|
91
|
+
diagnostics: [],
|
|
92
|
+
diagnosticsTruncated: false,
|
|
93
|
+
error: { version: 1, code: "cancelled", message: "Formatter-linter check was cancelled" },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function validSummary(value) {
|
|
97
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
98
|
+
return false;
|
|
99
|
+
const summary = value;
|
|
100
|
+
const keys = ["targetsChecked", "total", "errors", "warnings", "infos", "formatIssues"];
|
|
101
|
+
if (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary)))
|
|
102
|
+
return false;
|
|
103
|
+
const counts = keys.map((key) => summary[key]);
|
|
104
|
+
if (counts.some((count) => !Number.isSafeInteger(count) || count < 0 || count > 10_000_000))
|
|
105
|
+
return false;
|
|
106
|
+
return (counts[2] + counts[3] + counts[4] === counts[1] &&
|
|
107
|
+
counts[5] <= counts[1]);
|
|
108
|
+
}
|
|
109
|
+
function validError(value) {
|
|
110
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
111
|
+
return false;
|
|
112
|
+
const error = value;
|
|
113
|
+
if (Object.keys(error).some((key) => !["version", "code", "message", "causeCode"].includes(key)))
|
|
114
|
+
return false;
|
|
115
|
+
return (error.version === 1 &&
|
|
116
|
+
typeof error.code === "string" &&
|
|
117
|
+
/^[a-z][a-z0-9_]{0,127}$/u.test(error.code) &&
|
|
118
|
+
typeof error.message === "string" &&
|
|
119
|
+
error.message.trim() !== "" &&
|
|
120
|
+
error.message.length <= 512 &&
|
|
121
|
+
(error.causeCode === undefined ||
|
|
122
|
+
(typeof error.causeCode === "string" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode))));
|
|
123
|
+
}
|
|
124
|
+
function boundedResult(value, operationId) {
|
|
125
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
126
|
+
return adapterFailed(operationId);
|
|
127
|
+
const result = value;
|
|
128
|
+
if (result.status === "succeeded")
|
|
129
|
+
return projectFormatterLinterResultV1(result, operationId) ?? adapterFailed(operationId);
|
|
130
|
+
if (result.version !== 1 ||
|
|
131
|
+
result.operationId !== operationId ||
|
|
132
|
+
!["failed", "cancelled", "needs_user", "unsupported"].includes(result.status) ||
|
|
133
|
+
!Array.isArray(result.diagnostics) ||
|
|
134
|
+
result.diagnostics.length !== 0 ||
|
|
135
|
+
result.diagnosticsTruncated !== false ||
|
|
136
|
+
result.error === undefined ||
|
|
137
|
+
(result.reportArtifactRef !== undefined &&
|
|
138
|
+
(typeof result.reportArtifactRef !== "string" ||
|
|
139
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(result.reportArtifactRef))) ||
|
|
140
|
+
Object.keys(result).some((key) => ![
|
|
141
|
+
"version",
|
|
142
|
+
"operationId",
|
|
143
|
+
"status",
|
|
144
|
+
"summary",
|
|
145
|
+
"diagnostics",
|
|
146
|
+
"diagnosticsTruncated",
|
|
147
|
+
"reportArtifactRef",
|
|
148
|
+
"error",
|
|
149
|
+
].includes(key)))
|
|
150
|
+
return adapterFailed(operationId);
|
|
151
|
+
if (!validSummary(result.summary) || !validError(result.error))
|
|
152
|
+
return adapterFailed(operationId);
|
|
153
|
+
const error = result.error;
|
|
154
|
+
return {
|
|
155
|
+
version: 1,
|
|
156
|
+
operationId,
|
|
157
|
+
status: result.status,
|
|
158
|
+
summary: result.summary,
|
|
159
|
+
diagnostics: [],
|
|
160
|
+
diagnosticsTruncated: false,
|
|
161
|
+
...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef }),
|
|
162
|
+
error: {
|
|
163
|
+
version: 1,
|
|
164
|
+
code: error.code,
|
|
165
|
+
message: sanitizeFormatterLinterTextV1(error.message),
|
|
166
|
+
...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const CANCELLATION_V1 = Symbol("formatter-linter-capability-cancelled");
|
|
171
|
+
function runWithCancellation(run, signal) {
|
|
172
|
+
if (signal === undefined)
|
|
173
|
+
return run();
|
|
174
|
+
let onAbort;
|
|
175
|
+
const cancellation = new Promise((resolve) => {
|
|
176
|
+
onAbort = () => resolve(CANCELLATION_V1);
|
|
177
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
178
|
+
});
|
|
179
|
+
const pending = run();
|
|
180
|
+
return Promise.race([pending, cancellation]).finally(() => signal.removeEventListener("abort", onAbort));
|
|
181
|
+
}
|
|
182
|
+
/** Registers only a read-only report tool. It never offers a formatting write action. */
|
|
183
|
+
export function createFormatterLinterCapabilityFactoryV1(runtime) {
|
|
184
|
+
return (api) => {
|
|
185
|
+
api.registerTool({
|
|
186
|
+
name: "formatter_linter_check",
|
|
187
|
+
label: "Check formatting and lint",
|
|
188
|
+
description: "Run a host-approved lint or formatting check and return bounded diagnostics",
|
|
189
|
+
parameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),
|
|
190
|
+
execute: async (first, second, third) => {
|
|
191
|
+
let invocationResult;
|
|
192
|
+
try {
|
|
193
|
+
invocationResult = invocation(first, second);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return invalidInput("profile-runtime-tool-call");
|
|
197
|
+
}
|
|
198
|
+
const { input, operationId } = invocationResult;
|
|
199
|
+
const signal = signalFrom(third);
|
|
200
|
+
if (signal?.aborted)
|
|
201
|
+
return cancelled(operationId);
|
|
202
|
+
let requestInput;
|
|
203
|
+
try {
|
|
204
|
+
if (!Value.Check(Type.Object({ request: requestSchema }, { additionalProperties: false }), input))
|
|
205
|
+
return invalidInput(operationId);
|
|
206
|
+
requestInput = objectInput(input.request, undefined);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return invalidInput(operationId);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const output = await runWithCancellation(() => runtime.run({
|
|
213
|
+
version: 1,
|
|
214
|
+
operationId,
|
|
215
|
+
workspaceRef: requestInput.workspaceRef,
|
|
216
|
+
language: requestInput.language,
|
|
217
|
+
tool: requestInput.tool,
|
|
218
|
+
operation: requestInput.operation,
|
|
219
|
+
targetRef: requestInput.targetRef,
|
|
220
|
+
...(requestInput.timeoutMs === undefined
|
|
221
|
+
? {}
|
|
222
|
+
: { timeoutMs: requestInput.timeoutMs }),
|
|
223
|
+
...(requestInput.maxDiagnostics === undefined
|
|
224
|
+
? {}
|
|
225
|
+
: { maxDiagnostics: requestInput.maxDiagnostics }),
|
|
226
|
+
}, signal), signal);
|
|
227
|
+
if (output === CANCELLATION_V1)
|
|
228
|
+
return cancelled(operationId);
|
|
229
|
+
return boundedResult(output, operationId);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return adapterFailed(operationId);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport { Value } from \"typebox/value\";\nimport {\n\tFORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1,\n\tprojectFormatterLinterResultV1,\n\tsanitizeFormatterLinterTextV1,\n} from \"./node-adapter.ts\";\nimport type { FormatterLinterAdapterV1, FormatterLinterResultV1 } from \"./types.ts\";\n\nconst opaqueRef = Type.String({\n\tminLength: 1,\n\tmaxLength: FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tpattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\",\n});\nconst requestSchema = Type.Object(\n\t{\n\t\tworkspaceRef: opaqueRef,\n\t\tlanguage: Type.Union([\n\t\t\tType.Literal(\"typescript\"),\n\t\t\tType.Literal(\"javascript\"),\n\t\t\tType.Literal(\"json\"),\n\t\t\tType.Literal(\"css\"),\n\t\t\tType.Literal(\"html\"),\n\t\t\tType.Literal(\"markdown\"),\n\t\t\tType.Literal(\"yaml\"),\n\t\t\tType.Literal(\"python\"),\n\t\t\tType.Literal(\"go\"),\n\t\t\tType.Literal(\"rust\"),\n\t\t\tType.Literal(\"java\"),\n\t\t\tType.Literal(\"c\"),\n\t\t\tType.Literal(\"cpp\"),\n\t\t\tType.Literal(\"csharp\"),\n\t\t\tType.Literal(\"ruby\"),\n\t\t\tType.Literal(\"php\"),\n\t\t]),\n\t\ttool: Type.Union([\n\t\t\tType.Literal(\"eslint\"),\n\t\t\tType.Literal(\"prettier\"),\n\t\t\tType.Literal(\"biome\"),\n\t\t\tType.Literal(\"ruff\"),\n\t\t\tType.Literal(\"gofmt\"),\n\t\t\tType.Literal(\"black\"),\n\t\t\tType.Literal(\"clang-format\"),\n\t\t\tType.Literal(\"golangci-lint\"),\n\t\t]),\n\t\toperation: Type.Union([Type.Literal(\"lint\"), Type.Literal(\"format-check\")]),\n\t\ttargetRef: opaqueRef,\n\t\ttimeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 120_000 })),\n\t\tmaxDiagnostics: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Formatter-linter input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\tconst candidate = typeof first === \"string\" && first.trim() !== \"\" ? first : \"profile-runtime-tool-call\";\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId:\n\t\t\tcandidate.length <= FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1 ? candidate : \"profile-runtime-tool-call\",\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction emptySummary(): FormatterLinterResultV1[\"summary\"] {\n\treturn { targetsChecked: 0, total: 0, errors: 0, warnings: 0, infos: 0, formatIssues: 0 };\n}\n\nfunction adapterFailed(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"Formatter-linter adapter returned an invalid result\" },\n\t};\n}\n\nfunction invalidInput(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message: \"Formatter-linter request is invalid\" },\n\t};\n}\n\nfunction cancelled(operationId: string): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"Formatter-linter check was cancelled\" },\n\t};\n}\n\nfunction validSummary(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst summary = value as Record<string, unknown>;\n\tconst keys = [\"targetsChecked\", \"total\", \"errors\", \"warnings\", \"infos\", \"formatIssues\"];\n\tif (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary))) return false;\n\tconst counts = keys.map((key) => summary[key]);\n\tif (counts.some((count) => !Number.isSafeInteger(count) || (count as number) < 0 || (count as number) > 10_000_000))\n\t\treturn false;\n\treturn (\n\t\t(counts[2] as number) + (counts[3] as number) + (counts[4] as number) === counts[1] &&\n\t\t(counts[5] as number) <= counts[1]\n\t);\n}\n\nfunction validError(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst error = value as Record<string, unknown>;\n\tif (Object.keys(error).some((key) => ![\"version\", \"code\", \"message\", \"causeCode\"].includes(key))) return false;\n\treturn (\n\t\terror.version === 1 &&\n\t\ttypeof error.code === \"string\" &&\n\t\t/^[a-z][a-z0-9_]{0,127}$/u.test(error.code) &&\n\t\ttypeof error.message === \"string\" &&\n\t\terror.message.trim() !== \"\" &&\n\t\terror.message.length <= 512 &&\n\t\t(error.causeCode === undefined ||\n\t\t\t(typeof error.causeCode === \"string\" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode)))\n\t);\n}\n\nfunction boundedResult(value: unknown, operationId: string): FormatterLinterResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return adapterFailed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tif (result.status === \"succeeded\")\n\t\treturn projectFormatterLinterResultV1(result, operationId) ?? adapterFailed(operationId);\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\t!([\"failed\", \"cancelled\", \"needs_user\", \"unsupported\"] as readonly string[]).includes(result.status as string) ||\n\t\t!Array.isArray(result.diagnostics) ||\n\t\tresult.diagnostics.length !== 0 ||\n\t\tresult.diagnosticsTruncated !== false ||\n\t\tresult.error === undefined ||\n\t\t(result.reportArtifactRef !== undefined &&\n\t\t\t(typeof result.reportArtifactRef !== \"string\" ||\n\t\t\t\t!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(result.reportArtifactRef))) ||\n\t\tObject.keys(result).some(\n\t\t\t(key) =>\n\t\t\t\t![\n\t\t\t\t\t\"version\",\n\t\t\t\t\t\"operationId\",\n\t\t\t\t\t\"status\",\n\t\t\t\t\t\"summary\",\n\t\t\t\t\t\"diagnostics\",\n\t\t\t\t\t\"diagnosticsTruncated\",\n\t\t\t\t\t\"reportArtifactRef\",\n\t\t\t\t\t\"error\",\n\t\t\t\t].includes(key),\n\t\t)\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (!validSummary(result.summary) || !validError(result.error)) return adapterFailed(operationId);\n\tconst error = result.error as {\n\t\treadonly version: 1;\n\t\treadonly code: string;\n\t\treadonly message: string;\n\t\treadonly causeCode?: string;\n\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: result.status as FormatterLinterResultV1[\"status\"],\n\t\tsummary: result.summary as FormatterLinterResultV1[\"summary\"],\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\t...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef as string }),\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeFormatterLinterTextV1(error.message),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t},\n\t};\n}\n\nconst CANCELLATION_V1 = Symbol(\"formatter-linter-capability-cancelled\");\n\nfunction runWithCancellation(\n\trun: () => Promise<FormatterLinterResultV1>,\n\tsignal: AbortSignal | undefined,\n): Promise<FormatterLinterResultV1 | typeof CANCELLATION_V1> {\n\tif (signal === undefined) return run();\n\tlet onAbort!: () => void;\n\tconst cancellation = new Promise<typeof CANCELLATION_V1>((resolve) => {\n\t\tonAbort = () => resolve(CANCELLATION_V1);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n\tconst pending = run();\n\treturn Promise.race([pending, cancellation]).finally(() => signal.removeEventListener(\"abort\", onAbort));\n}\n\n/** Registers only a read-only report tool. It never offers a formatting write action. */\nexport function createFormatterLinterCapabilityFactoryV1(\n\truntime: FormatterLinterAdapterV1,\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tapi.registerTool({\n\t\t\tname: \"formatter_linter_check\",\n\t\t\tlabel: \"Check formatting and lint\",\n\t\t\tdescription: \"Run a host-approved lint or formatting check and return bounded diagnostics\",\n\t\t\tparameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(\"profile-runtime-tool-call\");\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet requestInput: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\tif (!Value.Check(Type.Object({ request: requestSchema }, { additionalProperties: false }), input))\n\t\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t\trequestInput = objectInput(input.request, undefined);\n\t\t\t\t} catch {\n\t\t\t\t\treturn invalidInput(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\truntime.run(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\t\tworkspaceRef: requestInput.workspaceRef as string,\n\t\t\t\t\t\t\t\t\tlanguage: requestInput.language as never,\n\t\t\t\t\t\t\t\t\ttool: requestInput.tool as never,\n\t\t\t\t\t\t\t\t\toperation: requestInput.operation as never,\n\t\t\t\t\t\t\t\t\ttargetRef: requestInput.targetRef as string,\n\t\t\t\t\t\t\t\t\t...(requestInput.timeoutMs === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { timeoutMs: requestInput.timeoutMs as number }),\n\t\t\t\t\t\t\t\t\t...(requestInput.maxDiagnostics === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { maxDiagnostics: requestInput.maxDiagnostics as number }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t);\n\t\t\t\t\tif (output === CANCELLATION_V1) return cancelled(operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
3
|
+
*
|
|
4
|
+
* The adapter is deliberately constructed without host injection: this plugin
|
|
5
|
+
* never discovers or executes a local formatter/linter by itself, so the
|
|
6
|
+
* registered `formatter_linter_check` tool reports an explicit `unsupported`
|
|
7
|
+
* result until a host binds an approved runner through its own profile
|
|
8
|
+
* machinery (fail-closed by design, same contract as the embedded build).
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("@agent-forge/plugin-sdk").CapabilityPluginSyncFactory;
|
|
11
|
+
export default _default;
|
|
12
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;;AACH,wBAA8F","sourcesContent":["import { createFormatterLinterCapabilityFactoryV1 } from \"./capability.ts\";\nimport { createNodeFormatterLinterAdapterV1 } from \"./node-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is deliberately constructed without host injection: this plugin\n * never discovers or executes a local formatter/linter by itself, so the\n * registered `formatter_linter_check` tool reports an explicit `unsupported`\n * result until a host binds an approved runner through its own profile\n * machinery (fail-closed by design, same contract as the embedded build).\n */\nexport default createFormatterLinterCapabilityFactoryV1(createNodeFormatterLinterAdapterV1());\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createFormatterLinterCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
import { createNodeFormatterLinterAdapterV1 } from "./node-adapter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
5
|
+
*
|
|
6
|
+
* The adapter is deliberately constructed without host injection: this plugin
|
|
7
|
+
* never discovers or executes a local formatter/linter by itself, so the
|
|
8
|
+
* registered `formatter_linter_check` tool reports an explicit `unsupported`
|
|
9
|
+
* result until a host binds an approved runner through its own profile
|
|
10
|
+
* machinery (fail-closed by design, same contract as the embedded build).
|
|
11
|
+
*/
|
|
12
|
+
export default createFormatterLinterCapabilityFactoryV1(createNodeFormatterLinterAdapterV1());
|
|
13
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,kCAAkC,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAe,wCAAwC,CAAC,kCAAkC,EAAE,CAAC,CAAC","sourcesContent":["import { createFormatterLinterCapabilityFactoryV1 } from \"./capability.ts\";\nimport { createNodeFormatterLinterAdapterV1 } from \"./node-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is deliberately constructed without host injection: this plugin\n * never discovers or executes a local formatter/linter by itself, so the\n * registered `formatter_linter_check` tool reports an explicit `unsupported`\n * result until a host binds an approved runner through its own profile\n * machinery (fail-closed by design, same contract as the embedded build).\n */\nexport default createFormatterLinterCapabilityFactoryV1(createNodeFormatterLinterAdapterV1());\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createFormatterLinterCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export { createDeterministicFormatterLinterAdapterV1, createNodeFormatterLinterAdapterV1, FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1, FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1, FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1, FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1, projectFormatterLinterResultV1, sanitizeFormatterLinterTextV1, } from "./node-adapter.ts";
|
|
3
|
+
export { sanitizeBinaryOutput } from "./sanitize.ts";
|
|
4
|
+
export type * from "./types.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EACN,2CAA2C,EAC3C,kCAAkC,EAClC,qCAAqC,EACrC,wCAAwC,EACxC,0CAA0C,EAC1C,oCAAoC,EACpC,8BAA8B,EAC9B,6BAA6B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,mBAAmB,YAAY,CAAC","sourcesContent":["export { createFormatterLinterCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tcreateDeterministicFormatterLinterAdapterV1,\n\tcreateNodeFormatterLinterAdapterV1,\n\tFORMATTER_LINTER_MAX_MESSAGE_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OUTPUT_BYTES_V1,\n\tprojectFormatterLinterResultV1,\n\tsanitizeFormatterLinterTextV1,\n} from \"./node-adapter.ts\";\nexport { sanitizeBinaryOutput } from \"./sanitize.ts\";\nexport type * from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createFormatterLinterCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { createDeterministicFormatterLinterAdapterV1, createNodeFormatterLinterAdapterV1, FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1, FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1, FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1, FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1, projectFormatterLinterResultV1, sanitizeFormatterLinterTextV1, } from "./node-adapter.js";
|
|
3
|
+
export { sanitizeBinaryOutput } from "./sanitize.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wCAAwC,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EACN,2CAA2C,EAC3C,kCAAkC,EAClC,qCAAqC,EACrC,wCAAwC,EACxC,0CAA0C,EAC1C,oCAAoC,EACpC,8BAA8B,EAC9B,6BAA6B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export { createFormatterLinterCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tcreateDeterministicFormatterLinterAdapterV1,\n\tcreateNodeFormatterLinterAdapterV1,\n\tFORMATTER_LINTER_MAX_MESSAGE_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1,\n\tFORMATTER_LINTER_MAX_OUTPUT_BYTES_V1,\n\tprojectFormatterLinterResultV1,\n\tsanitizeFormatterLinterTextV1,\n} from \"./node-adapter.ts\";\nexport { sanitizeBinaryOutput } from \"./sanitize.ts\";\nexport type * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FormatterLinterAdapterV1, type FormatterLinterDeterministicFixtureV1, type FormatterLinterResultV1, type NodeFormatterLinterAdapterOptionsV1 } from "./types.ts";
|
|
2
|
+
export declare const FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1: number;
|
|
3
|
+
export declare const FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1 = 512;
|
|
4
|
+
export declare const FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
5
|
+
export declare const FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1 = 256;
|
|
6
|
+
/** Removes host paths, credentials, binary content, and excess text before egress. */
|
|
7
|
+
export declare function sanitizeFormatterLinterTextV1(value: string, maximum?: number): string;
|
|
8
|
+
/** Strictly projects adapter egress to a bounded path-free success DTO. */
|
|
9
|
+
export declare function projectFormatterLinterResultV1(value: unknown, operationId: string): FormatterLinterResultV1 | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a read-only adapter. No host injection means `unsupported`; it never
|
|
12
|
+
* discovers or executes a local formatter/linter by itself.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createNodeFormatterLinterAdapterV1(options?: NodeFormatterLinterAdapterOptionsV1): FormatterLinterAdapterV1;
|
|
15
|
+
/** Deterministic fixture adapter for tests; it never invokes local tooling. */
|
|
16
|
+
export declare function createDeterministicFormatterLinterAdapterV1(fixtures: readonly FormatterLinterDeterministicFixtureV1[]): FormatterLinterAdapterV1;
|
|
17
|
+
//# sourceMappingURL=node-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,wBAAwB,EAC7B,KAAK,qCAAqC,EAK1C,KAAK,uBAAuB,EAE5B,KAAK,mCAAmC,EACxC,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oCAAoC,QAAY,CAAC;AAC9D,eAAO,MAAM,qCAAqC,MAAM,CAAC;AACzD,eAAO,MAAM,0CAA0C,MAAM,CAAC;AAC9D,eAAO,MAAM,wCAAwC,MAAM,CAAC;AA+D5D,sFAAsF;AACtF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAwC,GAAG,MAAM,CAEpH;AA6KD,2EAA2E;AAC3E,wBAAgB,8BAA8B,CAC7C,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GACjB,uBAAuB,GAAG,SAAS,CAoCrC;AAoFD;;;GAGG;AACH,wBAAgB,kCAAkC,CACjD,OAAO,GAAE,mCAAwC,GAC/C,wBAAwB,CA0C1B;AAED,+EAA+E;AAC/E,wBAAgB,2CAA2C,CAC1D,QAAQ,EAAE,SAAS,qCAAqC,EAAE,GACxD,wBAAwB,CAsB1B","sourcesContent":["import type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { projectPluginContextV1 } from \"@agent-forge/plugin-sdk\";\nimport { sanitizeBinaryOutput } from \"./sanitize.ts\";\nimport {\n\tFORMATTER_LINTER_HOST_ADAPTER_ID_V1,\n\tFORMATTER_LINTER_HOST_ADAPTER_VERSION_V1,\n\tFORMATTER_LINTER_MAX_DIAGNOSTICS_V1,\n\ttype FormatterLinterAdapterV1,\n\ttype FormatterLinterDeterministicFixtureV1,\n\ttype FormatterLinterDiagnosticV1,\n\ttype FormatterLinterExecutionV1,\n\ttype FormatterLinterHostResultV1,\n\ttype FormatterLinterRequestV1,\n\ttype FormatterLinterResultV1,\n\ttype FormatterLinterSummaryV1,\n\ttype NodeFormatterLinterAdapterOptionsV1,\n} from \"./types.ts\";\n\nexport const FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1 = 64 * 1024;\nexport const FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1 = 512;\nexport const FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1 = 256;\nexport const FORMATTER_LINTER_MAX_OPAQUE_REF_CHARS_V1 = 256;\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst MAX_HOST_DIAGNOSTICS_V1 = 4_096;\nconst MAX_POSITION_V1 = 10_000_000;\nconst MAX_RULE_ID_CHARS_V1 = 128;\nconst MAX_HOST_MESSAGE_CHARS_V1 = 8_192;\nconst MAX_TARGETS_V1 = 10_000_000;\n\nconst OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\nconst RULE_ID_V1 = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;\nconst LANGUAGES_V1 = new Set([\n\t\"typescript\",\n\t\"javascript\",\n\t\"json\",\n\t\"css\",\n\t\"html\",\n\t\"markdown\",\n\t\"yaml\",\n\t\"python\",\n\t\"go\",\n\t\"rust\",\n\t\"java\",\n\t\"c\",\n\t\"cpp\",\n\t\"csharp\",\n\t\"ruby\",\n\t\"php\",\n]);\nconst TOOLS_V1 = new Set([\"eslint\", \"prettier\", \"biome\", \"ruff\", \"gofmt\", \"black\", \"clang-format\", \"golangci-lint\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {\n\treturn Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction isOpaqueRef(value: unknown): value is string {\n\treturn typeof value === \"string\" && OPAQUE_REF_V1.test(value);\n}\n\nfunction validPositiveInteger(value: unknown, maximum: number): value is number {\n\treturn Number.isSafeInteger(value) && (value as number) >= 1 && (value as number) <= maximum;\n}\n\nfunction validNonNegativeInteger(value: unknown, maximum: number): value is number {\n\treturn Number.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= maximum;\n}\n\nfunction safeText(value: string, maximum: number): string {\n\tconst redacted = sanitizeBinaryOutput(value)\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\tconst chars = [...redacted];\n\treturn chars.length <= maximum ? redacted : chars.slice(0, maximum).join(\"\");\n}\n\n/** Removes host paths, credentials, binary content, and excess text before egress. */\nexport function sanitizeFormatterLinterTextV1(value: string, maximum = FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1): string {\n\treturn safeText(value, maximum);\n}\n\nfunction emptySummary(): FormatterLinterSummaryV1 {\n\treturn { targetsChecked: 0, total: 0, errors: 0, warnings: 0, infos: 0, formatIssues: 0 };\n}\n\nfunction resultError(\n\toperationId: string,\n\tstatus: \"failed\" | \"cancelled\" | \"unsupported\",\n\tcode: string,\n\tmessage: string,\n): FormatterLinterResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus,\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code, message },\n\t};\n}\n\nfunction invalidInput(operationId: string, message: string): FormatterLinterResultV1 {\n\treturn resultError(operationId, \"failed\", \"invalid_input\", message);\n}\n\nfunction adapterFailed(operationId: string): FormatterLinterResultV1 {\n\treturn resultError(operationId, \"failed\", \"adapter_failed\", \"Formatter-linter adapter returned an invalid result\");\n}\n\nfunction unsupported(operationId: string): FormatterLinterResultV1 {\n\treturn resultError(\n\t\toperationId,\n\t\t\"unsupported\",\n\t\t\"formatter_linter_unavailable\",\n\t\t\"No formatter-linter runner is bound by this host\",\n\t);\n}\n\nfunction cancelled(operationId: string): FormatterLinterResultV1 {\n\treturn resultError(operationId, \"cancelled\", \"cancelled\", \"Formatter-linter check was cancelled\");\n}\n\nfunction timedOut(operationId: string): FormatterLinterResultV1 {\n\treturn resultError(operationId, \"failed\", \"timeout\", \"Formatter-linter check timed out\");\n}\n\nfunction validateRequest(request: FormatterLinterRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (!isOpaqueRef(request.workspaceRef)) return \"workspaceRef must be a safe opaque host handle\";\n\tif (!isOpaqueRef(request.targetRef)) return \"targetRef must be a safe opaque host handle\";\n\tif (typeof request.language !== \"string\" || !LANGUAGES_V1.has(request.language)) return \"language is not supported\";\n\tif (typeof request.tool !== \"string\" || !TOOLS_V1.has(request.tool)) return \"tool is not supported\";\n\tif (request.operation !== \"lint\" && request.operation !== \"format-check\") return \"operation is invalid\";\n\tif (request.timeoutMs !== undefined && !validPositiveInteger(request.timeoutMs, MAX_TIMEOUT_MS_V1))\n\t\treturn `timeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`;\n\tif (\n\t\trequest.maxDiagnostics !== undefined &&\n\t\t!validPositiveInteger(request.maxDiagnostics, FORMATTER_LINTER_MAX_DIAGNOSTICS_V1)\n\t)\n\t\treturn `maxDiagnostics must be a positive integer at most ${FORMATTER_LINTER_MAX_DIAGNOSTICS_V1}`;\n\treturn undefined;\n}\n\nfunction projectDiagnostic(value: unknown): FormatterLinterDiagnosticV1 | undefined {\n\tif (\n\t\t!isRecord(value) ||\n\t\t!hasOnlyKeys(value, [\"targetRef\", \"category\", \"severity\", \"ruleId\", \"line\", \"column\", \"message\"])\n\t)\n\t\treturn undefined;\n\tif (!isOpaqueRef(value.targetRef)) return undefined;\n\tif (value.category !== \"lint\" && value.category !== \"format\") return undefined;\n\tif (value.severity !== \"error\" && value.severity !== \"warning\" && value.severity !== \"info\") return undefined;\n\tif (\n\t\tvalue.ruleId !== undefined &&\n\t\t(typeof value.ruleId !== \"string\" || value.ruleId.length > MAX_RULE_ID_CHARS_V1 || !RULE_ID_V1.test(value.ruleId))\n\t)\n\t\treturn undefined;\n\tif (value.line !== undefined && !validPositiveInteger(value.line, MAX_POSITION_V1)) return undefined;\n\tif (value.column !== undefined && !validPositiveInteger(value.column, MAX_POSITION_V1)) return undefined;\n\tif (\n\t\ttypeof value.message !== \"string\" ||\n\t\tvalue.message.trim() === \"\" ||\n\t\tvalue.message.length > MAX_HOST_MESSAGE_CHARS_V1\n\t)\n\t\treturn undefined;\n\treturn {\n\t\ttargetRef: value.targetRef,\n\t\tcategory: value.category,\n\t\tseverity: value.severity,\n\t\t...(value.ruleId === undefined ? {} : { ruleId: value.ruleId }),\n\t\t...(value.line === undefined ? {} : { line: value.line }),\n\t\t...(value.column === undefined ? {} : { column: value.column }),\n\t\tmessage: safeText(value.message, FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1),\n\t};\n}\n\nfunction summaryFor(\n\ttargetsChecked: number,\n\tdiagnostics: readonly FormatterLinterDiagnosticV1[],\n): FormatterLinterSummaryV1 {\n\tconst summary = {\n\t\ttargetsChecked,\n\t\ttotal: diagnostics.length,\n\t\terrors: 0,\n\t\twarnings: 0,\n\t\tinfos: 0,\n\t\tformatIssues: 0,\n\t};\n\tfor (const diagnostic of diagnostics) {\n\t\tif (diagnostic.severity === \"error\") summary.errors += 1;\n\t\tif (diagnostic.severity === \"warning\") summary.warnings += 1;\n\t\tif (diagnostic.severity === \"info\") summary.infos += 1;\n\t\tif (diagnostic.category === \"format\") summary.formatIssues += 1;\n\t}\n\treturn summary;\n}\n\nfunction contextBoundResult(result: FormatterLinterResultV1): FormatterLinterResultV1 | undefined {\n\tfor (let count = result.diagnostics.length; count >= 0; count--) {\n\t\tconst diagnostics = result.diagnostics.slice(0, count);\n\t\tconst candidate: FormatterLinterResultV1 = {\n\t\t\t...result,\n\t\t\tdiagnostics,\n\t\t\tdiagnosticsTruncated: result.diagnosticsTruncated || count < result.diagnostics.length,\n\t\t};\n\t\tconst projection = projectPluginContextV1(\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: candidate.operationId,\n\t\t\t\titems: [\n\t\t\t\t\t{ ...candidate.summary } as unknown as JsonValue,\n\t\t\t\t\t...diagnostics.map((diagnostic) => ({ ...diagnostic }) as unknown as JsonValue),\n\t\t\t\t],\n\t\t\t\t...(candidate.reportArtifactRef === undefined ? {} : { outputArtifactRef: candidate.reportArtifactRef }),\n\t\t\t\ttruncated: candidate.diagnosticsTruncated,\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxOutputBytes: FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1,\n\t\t\t\tmaxInlineItems: FORMATTER_LINTER_MAX_DIAGNOSTICS_V1 + 1,\n\t\t\t\tmaxCellChars: FORMATTER_LINTER_MAX_MESSAGE_CHARS_V1,\n\t\t\t},\n\t\t);\n\t\tif (\n\t\t\tprojection.status === \"succeeded\" &&\n\t\t\tnew TextEncoder().encode(JSON.stringify(candidate)).byteLength <= FORMATTER_LINTER_MAX_OUTPUT_BYTES_V1\n\t\t)\n\t\t\treturn candidate;\n\t}\n\treturn undefined;\n}\n\nfunction validSummary(value: unknown): value is FormatterLinterSummaryV1 {\n\tif (\n\t\t!isRecord(value) ||\n\t\t!hasOnlyKeys(value, [\"targetsChecked\", \"total\", \"errors\", \"warnings\", \"infos\", \"formatIssues\"])\n\t)\n\t\treturn false;\n\tconst counts = [value.targetsChecked, value.total, value.errors, value.warnings, value.infos, value.formatIssues];\n\tif (counts.some((count) => !validNonNegativeInteger(count, MAX_TARGETS_V1))) return false;\n\treturn (\n\t\t(value.errors as number) + (value.warnings as number) + (value.infos as number) === value.total &&\n\t\t(value.formatIssues as number) <= value.total\n\t);\n}\n\n/** Strictly projects adapter egress to a bounded path-free success DTO. */\nexport function projectFormatterLinterResultV1(\n\tvalue: unknown,\n\toperationId: string,\n): FormatterLinterResultV1 | undefined {\n\tif (\n\t\t!isRecord(value) ||\n\t\t!hasOnlyKeys(value, [\n\t\t\t\"version\",\n\t\t\t\"operationId\",\n\t\t\t\"status\",\n\t\t\t\"summary\",\n\t\t\t\"diagnostics\",\n\t\t\t\"diagnosticsTruncated\",\n\t\t\t\"reportArtifactRef\",\n\t\t\t\"error\",\n\t\t]) ||\n\t\tvalue.version !== 1 ||\n\t\tvalue.operationId !== operationId ||\n\t\tvalue.status !== \"succeeded\" ||\n\t\t!validSummary(value.summary) ||\n\t\t!Array.isArray(value.diagnostics) ||\n\t\tvalue.diagnostics.length > FORMATTER_LINTER_MAX_DIAGNOSTICS_V1 ||\n\t\ttypeof value.diagnosticsTruncated !== \"boolean\" ||\n\t\tvalue.error !== undefined ||\n\t\t(value.reportArtifactRef !== undefined && !isOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn undefined;\n\tconst diagnostics = value.diagnostics.map(projectDiagnostic);\n\tif (diagnostics.some((diagnostic) => diagnostic === undefined)) return undefined;\n\tconst projected: FormatterLinterResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"succeeded\",\n\t\tsummary: value.summary,\n\t\tdiagnostics: diagnostics as FormatterLinterDiagnosticV1[],\n\t\tdiagnosticsTruncated: value.diagnosticsTruncated,\n\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),\n\t};\n\treturn contextBoundResult(projected);\n}\n\nfunction completeResult(\n\toperationId: string,\n\tvalue: FormatterLinterHostResultV1,\n\tlimit: number,\n): FormatterLinterResultV1 {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"status\", \"targetsChecked\", \"diagnostics\", \"reportArtifactRef\"]))\n\t\treturn adapterFailed(operationId);\n\tif (value.status === \"unsupported\")\n\t\treturn Object.keys(value).length === 1 ? unsupported(operationId) : adapterFailed(operationId);\n\tif (\n\t\tvalue.status !== \"completed\" ||\n\t\t!validNonNegativeInteger(value.targetsChecked, MAX_TARGETS_V1) ||\n\t\t(value.diagnostics !== undefined && !Array.isArray(value.diagnostics)) ||\n\t\t(value.reportArtifactRef !== undefined && !isOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn adapterFailed(operationId);\n\tconst rawDiagnostics = value.diagnostics ?? [];\n\tif (rawDiagnostics.length > MAX_HOST_DIAGNOSTICS_V1) return adapterFailed(operationId);\n\tconst allDiagnostics = rawDiagnostics.map(projectDiagnostic);\n\tif (allDiagnostics.some((diagnostic) => diagnostic === undefined)) return adapterFailed(operationId);\n\tconst diagnostics = allDiagnostics as FormatterLinterDiagnosticV1[];\n\tconst visible = diagnostics.slice(0, Math.min(limit, FORMATTER_LINTER_MAX_DIAGNOSTICS_V1));\n\treturn (\n\t\tcontextBoundResult({\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"succeeded\",\n\t\t\tsummary: summaryFor(value.targetsChecked, diagnostics),\n\t\t\tdiagnostics: visible,\n\t\t\tdiagnosticsTruncated: diagnostics.length > visible.length,\n\t\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),\n\t\t}) ?? adapterFailed(operationId)\n\t);\n}\n\nconst CANCELLATION_V1 = Symbol(\"formatter-linter-cancelled\");\nconst TIMEOUT_V1 = Symbol(\"formatter-linter-timeout\");\n\nasync function runBounded<T>(\n\trun: (signal: AbortSignal) => Promise<T>,\n\ttimeoutMs: number,\n\tsignal: AbortSignal | undefined,\n): Promise<T | typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined> {\n\tconst controller = new AbortController();\n\tlet stop: typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined;\n\tconst abort = () => {\n\t\tstop ??= CANCELLATION_V1;\n\t\tcontroller.abort();\n\t};\n\tif (signal?.aborted) return CANCELLATION_V1;\n\tsignal?.addEventListener(\"abort\", abort, { once: true });\n\tconst timeout = setTimeout(() => {\n\t\tstop ??= TIMEOUT_V1;\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\tconst completed = Promise.resolve()\n\t\t.then(() => run(controller.signal))\n\t\t.then(\n\t\t\t(value) => value,\n\t\t\t() => undefined,\n\t\t);\n\tconst stopped = new Promise<typeof CANCELLATION_V1 | typeof TIMEOUT_V1>((resolve) => {\n\t\tcontroller.signal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tif (stop !== undefined) resolve(stop);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([completed, stopped]);\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", abort);\n\t}\n}\n\nfunction validExecution(value: unknown): value is FormatterLinterExecutionV1 {\n\treturn isRecord(value) && hasOnlyKeys(value, [\"executionRef\"]) && isOpaqueRef(value.executionRef);\n}\n\n/**\n * Creates a read-only adapter. No host injection means `unsupported`; it never\n * discovers or executes a local formatter/linter by itself.\n */\nexport function createNodeFormatterLinterAdapterV1(\n\toptions: NodeFormatterLinterAdapterOptionsV1 = {},\n): FormatterLinterAdapterV1 {\n\tconst defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS_V1;\n\tif (!validPositiveInteger(defaultTimeoutMs, MAX_TIMEOUT_MS_V1))\n\t\tthrow new Error(`defaultTimeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`);\n\treturn {\n\t\tadapterId: FORMATTER_LINTER_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: FORMATTER_LINTER_HOST_ADAPTER_VERSION_V1,\n\t\tasync run(request, signal) {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" &&\n\t\t\t\trequest.operationId.trim() !== \"\" &&\n\t\t\t\trequest.operationId.length <= FORMATTER_LINTER_MAX_OPERATION_ID_CHARS_V1\n\t\t\t\t\t? request.operationId\n\t\t\t\t\t: \"profile-runtime-tool-call\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return invalidInput(operationId, invalid);\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tif (options.resolver === undefined || options.runner === undefined) return unsupported(operationId);\n\t\t\tconst timeoutMs = request.timeoutMs ?? defaultTimeoutMs;\n\t\t\tconst resolution = await runBounded(\n\t\t\t\tasync (internalSignal) => ({ execution: await options.resolver!.resolve(request, internalSignal) }),\n\t\t\t\ttimeoutMs,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (resolution === CANCELLATION_V1) return cancelled(operationId);\n\t\t\tif (resolution === TIMEOUT_V1) return timedOut(operationId);\n\t\t\tif (resolution === undefined) return adapterFailed(operationId);\n\t\t\tconst execution = resolution.execution;\n\t\t\tif (execution === undefined) return unsupported(operationId);\n\t\t\tif (!validExecution(execution)) return adapterFailed(operationId);\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tconst raw = await runBounded(\n\t\t\t\t(internalSignal) => options.runner!.run({ execution, request }, internalSignal),\n\t\t\t\ttimeoutMs,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (raw === CANCELLATION_V1) return cancelled(operationId);\n\t\t\tif (raw === TIMEOUT_V1) return timedOut(operationId);\n\t\t\tif (raw === undefined) return adapterFailed(operationId);\n\t\t\treturn completeResult(operationId, raw, request.maxDiagnostics ?? FORMATTER_LINTER_MAX_DIAGNOSTICS_V1);\n\t\t},\n\t};\n}\n\n/** Deterministic fixture adapter for tests; it never invokes local tooling. */\nexport function createDeterministicFormatterLinterAdapterV1(\n\tfixtures: readonly FormatterLinterDeterministicFixtureV1[],\n): FormatterLinterAdapterV1 {\n\tconst fixtureByKey = new Map<string, FormatterLinterDeterministicFixtureV1>();\n\tfor (const fixture of fixtures) {\n\t\tconst key = `${fixture.workspaceRef}\\u0000${fixture.language}\\u0000${fixture.tool}\\u0000${fixture.operation}\\u0000${fixture.targetRef}`;\n\t\tif (fixtureByKey.has(key)) throw new Error(\"Deterministic formatter-linter fixtures must be unique\");\n\t\tfixtureByKey.set(key, fixture);\n\t}\n\treturn createNodeFormatterLinterAdapterV1({\n\t\tresolver: {\n\t\t\tasync resolve(request) {\n\t\t\t\tconst key = `${request.workspaceRef}\\u0000${request.language}\\u0000${request.tool}\\u0000${request.operation}\\u0000${request.targetRef}`;\n\t\t\t\treturn fixtureByKey.has(key) ? { executionRef: `fixture:${request.targetRef}` } : undefined;\n\t\t\t},\n\t\t},\n\t\trunner: {\n\t\t\tasync run(input) {\n\t\t\t\tconst request = input.request;\n\t\t\t\tconst key = `${request.workspaceRef}\\u0000${request.language}\\u0000${request.tool}\\u0000${request.operation}\\u0000${request.targetRef}`;\n\t\t\t\treturn fixtureByKey.get(key)!.result;\n\t\t\t},\n\t\t},\n\t});\n}\n"]}
|