@agenteer/stdlib 1.0.0-rc.1
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 +98 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/context/context_curator.d.ts +115 -0
- package/dist/context/context_curator.d.ts.map +1 -0
- package/dist/context/context_curator.js +132 -0
- package/dist/context/context_curator.js.map +1 -0
- package/dist/humans/approval_gate.d.ts +82 -0
- package/dist/humans/approval_gate.d.ts.map +1 -0
- package/dist/humans/approval_gate.js +76 -0
- package/dist/humans/approval_gate.js.map +1 -0
- package/dist/humans/ask_user.d.ts +79 -0
- package/dist/humans/ask_user.d.ts.map +1 -0
- package/dist/humans/ask_user.js +101 -0
- package/dist/humans/ask_user.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/meta/cross_check.d.ts +96 -0
- package/dist/meta/cross_check.d.ts.map +1 -0
- package/dist/meta/cross_check.js +165 -0
- package/dist/meta/cross_check.js.map +1 -0
- package/dist/meta/judge_with_stripped_ctx.d.ts +75 -0
- package/dist/meta/judge_with_stripped_ctx.d.ts.map +1 -0
- package/dist/meta/judge_with_stripped_ctx.js +151 -0
- package/dist/meta/judge_with_stripped_ctx.js.map +1 -0
- package/dist/meta/parallel_fanout.d.ts +83 -0
- package/dist/meta/parallel_fanout.d.ts.map +1 -0
- package/dist/meta/parallel_fanout.js +120 -0
- package/dist/meta/parallel_fanout.js.map +1 -0
- package/dist/meta/repair_loop.d.ts +90 -0
- package/dist/meta/repair_loop.d.ts.map +1 -0
- package/dist/meta/repair_loop.js +230 -0
- package/dist/meta/repair_loop.js.map +1 -0
- package/dist/planner/default_planner.d.ts +88 -0
- package/dist/planner/default_planner.d.ts.map +1 -0
- package/dist/planner/default_planner.js +156 -0
- package/dist/planner/default_planner.js.map +1 -0
- package/dist/primitives/file_read.d.ts +60 -0
- package/dist/primitives/file_read.d.ts.map +1 -0
- package/dist/primitives/file_read.js +68 -0
- package/dist/primitives/file_read.js.map +1 -0
- package/dist/primitives/file_write.d.ts +60 -0
- package/dist/primitives/file_write.d.ts.map +1 -0
- package/dist/primitives/file_write.js +66 -0
- package/dist/primitives/file_write.js.map +1 -0
- package/dist/primitives/llm_call.d.ts +85 -0
- package/dist/primitives/llm_call.d.ts.map +1 -0
- package/dist/primitives/llm_call.js +99 -0
- package/dist/primitives/llm_call.js.map +1 -0
- package/dist/primitives/shell_exec.d.ts +66 -0
- package/dist/primitives/shell_exec.d.ts.map +1 -0
- package/dist/primitives/shell_exec.js +106 -0
- package/dist/primitives/shell_exec.js.map +1 -0
- package/dist/primitives/tool_call.d.ts +62 -0
- package/dist/primitives/tool_call.d.ts.map +1 -0
- package/dist/primitives/tool_call.js +69 -0
- package/dist/primitives/tool_call.js.map +1 -0
- package/dist/shared/capture.d.ts +34 -0
- package/dist/shared/capture.d.ts.map +1 -0
- package/dist/shared/capture.js +124 -0
- package/dist/shared/capture.js.map +1 -0
- package/dist/shared/ctx.d.ts +14 -0
- package/dist/shared/ctx.d.ts.map +1 -0
- package/dist/shared/ctx.js +48 -0
- package/dist/shared/ctx.js.map +1 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/validator.d.ts +47 -0
- package/dist/shared/validator.d.ts.map +1 -0
- package/dist/shared/validator.js +41 -0
- package/dist/shared/validator.js.map +1 -0
- package/dist/validators/compile.d.ts +83 -0
- package/dist/validators/compile.d.ts.map +1 -0
- package/dist/validators/compile.js +126 -0
- package/dist/validators/compile.js.map +1 -0
- package/dist/validators/json_schema_validate.d.ts +72 -0
- package/dist/validators/json_schema_validate.d.ts.map +1 -0
- package/dist/validators/json_schema_validate.js +85 -0
- package/dist/validators/json_schema_validate.js.map +1 -0
- package/dist/validators/regex_check.d.ts +64 -0
- package/dist/validators/regex_check.d.ts.map +1 -0
- package/dist/validators/regex_check.js +85 -0
- package/dist/validators/regex_check.js.map +1 -0
- package/dist/validators/test_run.d.ts +74 -0
- package/dist/validators/test_run.d.ts.map +1 -0
- package/dist/validators/test_run.js +149 -0
- package/dist/validators/test_run.js.map +1 -0
- package/dist/validators/typecheck.d.ts +61 -0
- package/dist/validators/typecheck.d.ts.map +1 -0
- package/dist/validators/typecheck.js +89 -0
- package/dist/validators/typecheck.js.map +1 -0
- package/package.json +61 -0
- package/src/context/context_curator.ts +154 -0
- package/src/humans/approval_gate.ts +101 -0
- package/src/humans/ask_user.ts +137 -0
- package/src/index.ts +149 -0
- package/src/meta/cross_check.ts +219 -0
- package/src/meta/judge_with_stripped_ctx.ts +171 -0
- package/src/meta/parallel_fanout.ts +142 -0
- package/src/meta/repair_loop.ts +267 -0
- package/src/planner/default_planner.ts +182 -0
- package/src/primitives/file_read.ts +82 -0
- package/src/primitives/file_write.ts +80 -0
- package/src/primitives/llm_call.ts +113 -0
- package/src/primitives/shell_exec.ts +122 -0
- package/src/primitives/tool_call.ts +84 -0
- package/src/shared/capture.ts +155 -0
- package/src/shared/ctx.ts +45 -0
- package/src/shared/index.ts +17 -0
- package/src/shared/validator.ts +51 -0
- package/src/validators/compile.ts +175 -0
- package/src/validators/json_schema_validate.ts +103 -0
- package/src/validators/regex_check.ts +107 -0
- package/src/validators/test_run.ts +199 -0
- package/src/validators/typecheck.ts +121 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agenteer/node-compile` — deterministic compile validator (sub-plan 03 §6).
|
|
3
|
+
*
|
|
4
|
+
* M4 ships a TypeScript (`tsc`) adapter only; the adapter interface below
|
|
5
|
+
* is what community packages (pyright/clippy/go-build) plug into. Choosing
|
|
6
|
+
* TS-only keeps the surface small and dogfoods from day one.
|
|
7
|
+
*
|
|
8
|
+
* Deterministic given identical project state + invoking flags; results
|
|
9
|
+
* are cacheable on `(manifest, input, ctx_slice)` per sub-plan 00 §11.
|
|
10
|
+
*
|
|
11
|
+
* `shell.exec` is declared statically. The caller-supplied `cwd` decides
|
|
12
|
+
* where tsc runs; capability scope for fs.read is declared dynamically
|
|
13
|
+
* from input.cwd so workflows only hand over the directory that matters.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import {
|
|
18
|
+
makeManifest,
|
|
19
|
+
type Node,
|
|
20
|
+
type NodeInput,
|
|
21
|
+
type NodeManifest,
|
|
22
|
+
type NodeResult,
|
|
23
|
+
type NodeRuntimeHandle,
|
|
24
|
+
authorizeOperation,
|
|
25
|
+
} from "@agenteer/core";
|
|
26
|
+
import {
|
|
27
|
+
failOutput,
|
|
28
|
+
passOutput,
|
|
29
|
+
runCommand,
|
|
30
|
+
tailOf,
|
|
31
|
+
ValidatorOutputSchema,
|
|
32
|
+
type ValidatorIssue,
|
|
33
|
+
type ValidatorOutput,
|
|
34
|
+
} from "../shared/index.js";
|
|
35
|
+
|
|
36
|
+
const MANIFEST: NodeManifest = makeManifest({
|
|
37
|
+
id: "@agenteer/node-compile",
|
|
38
|
+
name: "compile",
|
|
39
|
+
description:
|
|
40
|
+
"Run a compile step (v1: TypeScript `tsc`). Returns structured issue list; verdict is DATA.",
|
|
41
|
+
determinism: "deterministic",
|
|
42
|
+
required_actions: ["shell.exec:"],
|
|
43
|
+
dynamic_actions: true,
|
|
44
|
+
dynamic_action_spec: "fs.read:${input.cwd}",
|
|
45
|
+
tags: ["validator"],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const InputSchema = z.object({
|
|
49
|
+
/** Which language toolchain. v1 ships `typescript` only; `x-*` reserved for community. */
|
|
50
|
+
language: z.enum(["typescript"]),
|
|
51
|
+
cwd: z.string().min(1),
|
|
52
|
+
/** Explicit tsconfig path relative to cwd (default: `tsconfig.json`). */
|
|
53
|
+
tsconfig: z.string().optional(),
|
|
54
|
+
/** Additional flags (e.g. `--incremental false`). */
|
|
55
|
+
flags: z.array(z.string()).default([]),
|
|
56
|
+
timeout_ms: z.number().int().min(1).max(600_000).default(300_000),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
type Input = z.input<typeof InputSchema>;
|
|
60
|
+
|
|
61
|
+
export function compileFactory(): Node<Input, ValidatorOutput> {
|
|
62
|
+
return {
|
|
63
|
+
manifest: MANIFEST,
|
|
64
|
+
inputSchema: InputSchema,
|
|
65
|
+
outputSchema: ValidatorOutputSchema,
|
|
66
|
+
ctx: [],
|
|
67
|
+
model: null,
|
|
68
|
+
async execute(
|
|
69
|
+
input: NodeInput<Input>,
|
|
70
|
+
handle: NodeRuntimeHandle,
|
|
71
|
+
): Promise<NodeResult<ValidatorOutput>> {
|
|
72
|
+
const { language, cwd, tsconfig, flags } = input.original;
|
|
73
|
+
const timeout_ms = input.original.timeout_ms ?? 300_000;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
authorizeOperation(handle.granted, { kind: "shell.exec" });
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
kind: "failed",
|
|
80
|
+
reason: `permission_denied: ${err instanceof Error ? err.message : String(err)}`,
|
|
81
|
+
retryable: false,
|
|
82
|
+
evidence: { verdict: "fail" },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const adapter = ADAPTERS[language];
|
|
87
|
+
const command = adapter.command({ tsconfig, flags: flags ?? [] });
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const r = await runCommand(command, {
|
|
91
|
+
cwd,
|
|
92
|
+
timeout_ms,
|
|
93
|
+
signal: handle.signal,
|
|
94
|
+
});
|
|
95
|
+
const issues = adapter.parseIssues(r.stdout + "\n" + r.stderr);
|
|
96
|
+
const verdict: ValidatorOutput["verdict"] = r.exit_code === 0 && issues.length === 0 ? "pass" : "fail";
|
|
97
|
+
const out =
|
|
98
|
+
verdict === "pass"
|
|
99
|
+
? {
|
|
100
|
+
...passOutput(`${language} compile clean (${r.duration_ms}ms)`),
|
|
101
|
+
exit_code: r.exit_code,
|
|
102
|
+
}
|
|
103
|
+
: failOutput(
|
|
104
|
+
issues.length
|
|
105
|
+
? issues
|
|
106
|
+
: [
|
|
107
|
+
{
|
|
108
|
+
message: `${language} exited ${r.exit_code}${r.timed_out ? " (timed out)" : ""}`,
|
|
109
|
+
severity: "error" as const,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
`${language} compile failed: ${issues.length} issue(s)`,
|
|
113
|
+
{
|
|
114
|
+
exit_code: r.exit_code,
|
|
115
|
+
stdout_tail: tailOf(r.stdout),
|
|
116
|
+
stderr_tail: tailOf(r.stderr),
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
return { kind: "output", value: out, evidence: { verdict } };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "failed",
|
|
123
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
124
|
+
retryable: true,
|
|
125
|
+
evidence: { verdict: "fail" },
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const compileManifest = MANIFEST;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Compile adapter interface — community-extensible.
|
|
136
|
+
*
|
|
137
|
+
* A compile adapter maps an opaque `language` id to (a) a shell command
|
|
138
|
+
* and (b) a stdout/stderr parser that extracts structured issues. The
|
|
139
|
+
* parser must be best-effort: when tool output cannot be parsed, return
|
|
140
|
+
* an empty list and let the exit code carry the verdict.
|
|
141
|
+
*/
|
|
142
|
+
export interface CompileAdapter {
|
|
143
|
+
readonly language: string;
|
|
144
|
+
command(opts: { tsconfig?: string; flags: readonly string[] }): string;
|
|
145
|
+
parseIssues(combinedOutput: string): ValidatorIssue[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** tsc issue regex — `path(line,col): error TSxxxx: message`. */
|
|
149
|
+
const TSC_ISSUE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:\s+(.*)$/gm;
|
|
150
|
+
|
|
151
|
+
const TYPESCRIPT_ADAPTER: CompileAdapter = {
|
|
152
|
+
language: "typescript",
|
|
153
|
+
command(opts) {
|
|
154
|
+
const tsconfigArg = opts.tsconfig ? `--project ${JSON.stringify(opts.tsconfig)}` : "";
|
|
155
|
+
const extra = opts.flags.join(" ");
|
|
156
|
+
return `npx -y tsc --noEmit ${tsconfigArg} ${extra}`.replace(/\s+/g, " ").trim();
|
|
157
|
+
},
|
|
158
|
+
parseIssues(output) {
|
|
159
|
+
const issues: ValidatorIssue[] = [];
|
|
160
|
+
for (const m of output.matchAll(TSC_ISSUE_RE)) {
|
|
161
|
+
const [, file, line, col, sev, msg] = m;
|
|
162
|
+
issues.push({
|
|
163
|
+
path: `${file}:${line}:${col}`,
|
|
164
|
+
message: msg!.trim(),
|
|
165
|
+
code: "TS",
|
|
166
|
+
severity: sev === "warning" ? "warning" : "error",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return issues;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const ADAPTERS: Record<Input["language"], CompileAdapter> = {
|
|
174
|
+
typescript: TYPESCRIPT_ADAPTER,
|
|
175
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agenteer/node-json-schema-validate` — deterministic validator (sub-plan 03 §8).
|
|
3
|
+
*
|
|
4
|
+
* Validates a value against a Zod schema in-process. The "JSON Schema"
|
|
5
|
+
* name reflects the published wire contract (sub-plan 02 §2.4); we use
|
|
6
|
+
* Zod as the runtime validator for M2 since all stdlib consumers are
|
|
7
|
+
* TypeScript. ajv-backed JSON-Schema validation lands alongside the
|
|
8
|
+
* registry in M6.
|
|
9
|
+
*
|
|
10
|
+
* R3 note: validators emit `verdict: "fail"` as DATA on output, not as
|
|
11
|
+
* `Failed`. `Failed` is reserved for "couldn't run at all."
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import {
|
|
16
|
+
makeManifest,
|
|
17
|
+
type Node,
|
|
18
|
+
type NodeInput,
|
|
19
|
+
type NodeManifest,
|
|
20
|
+
type NodeResult,
|
|
21
|
+
} from "@agenteer/core";
|
|
22
|
+
|
|
23
|
+
const MANIFEST: NodeManifest = makeManifest({
|
|
24
|
+
id: "@agenteer/node-json-schema-validate",
|
|
25
|
+
name: "json_schema_validate",
|
|
26
|
+
description: "Validate a value against a caller-supplied schema; return verdict + errors.",
|
|
27
|
+
determinism: "deterministic",
|
|
28
|
+
tags: ["validator"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// We accept either a Zod schema reference or a raw "any" schema marker.
|
|
32
|
+
// At M2 the common case is Zod; the JSON-Schema path ships later.
|
|
33
|
+
const InputSchema = z.object({
|
|
34
|
+
/** Value to validate. */
|
|
35
|
+
value: z.unknown(),
|
|
36
|
+
/** Zod schema (pass a z.ZodType directly through the factory; runtime accepts unknown to satisfy I/O shape). */
|
|
37
|
+
schema: z.unknown(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const ValidationIssueSchema = z.object({
|
|
41
|
+
path: z.array(z.union([z.string(), z.number()])),
|
|
42
|
+
message: z.string(),
|
|
43
|
+
code: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const OutputSchema = z.object({
|
|
47
|
+
verdict: z.enum(["pass", "fail"]),
|
|
48
|
+
errors: z.array(ValidationIssueSchema),
|
|
49
|
+
value: z.unknown().optional(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
type Input = z.infer<typeof InputSchema>;
|
|
53
|
+
type Output = z.infer<typeof OutputSchema>;
|
|
54
|
+
|
|
55
|
+
export function jsonSchemaValidateFactory(): Node<Input, Output> {
|
|
56
|
+
return {
|
|
57
|
+
manifest: MANIFEST,
|
|
58
|
+
inputSchema: InputSchema,
|
|
59
|
+
outputSchema: OutputSchema,
|
|
60
|
+
ctx: [],
|
|
61
|
+
model: null,
|
|
62
|
+
async execute(input: NodeInput<Input>): Promise<NodeResult<Output>> {
|
|
63
|
+
const { value, schema } = input.original;
|
|
64
|
+
if (!isZodType(schema)) {
|
|
65
|
+
return {
|
|
66
|
+
kind: "failed",
|
|
67
|
+
reason: "invalid_input:schema_not_zod",
|
|
68
|
+
retryable: false,
|
|
69
|
+
evidence: { verdict: "fail" },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const parsed = schema.safeParse(value);
|
|
73
|
+
if (parsed.success) {
|
|
74
|
+
return {
|
|
75
|
+
kind: "output",
|
|
76
|
+
value: { verdict: "pass", errors: [], value: parsed.data },
|
|
77
|
+
evidence: { verdict: "pass" },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const errors = parsed.error.issues.map((i) => ({
|
|
81
|
+
path: [...i.path] as (string | number)[],
|
|
82
|
+
message: i.message,
|
|
83
|
+
code: i.code,
|
|
84
|
+
}));
|
|
85
|
+
return {
|
|
86
|
+
kind: "output",
|
|
87
|
+
value: { verdict: "fail", errors },
|
|
88
|
+
evidence: { verdict: "fail" },
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isZodType(x: unknown): x is z.ZodTypeAny {
|
|
95
|
+
return (
|
|
96
|
+
typeof x === "object" &&
|
|
97
|
+
x !== null &&
|
|
98
|
+
"_def" in (x as object) &&
|
|
99
|
+
typeof (x as { safeParse?: unknown }).safeParse === "function"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const jsonSchemaValidateManifest = MANIFEST;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agenteer/node-regex-check` — deterministic regex assertion validator.
|
|
3
|
+
*
|
|
4
|
+
* Runs N `must_match` / `must_not_match` patterns against an input string.
|
|
5
|
+
* Returns `verdict: "fail"` as data per ratified resolution — `Failed`
|
|
6
|
+
* is reserved for "couldn't run at all" (invalid regex, missing input).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import {
|
|
11
|
+
makeManifest,
|
|
12
|
+
type Node,
|
|
13
|
+
type NodeInput,
|
|
14
|
+
type NodeManifest,
|
|
15
|
+
type NodeResult,
|
|
16
|
+
} from "@agenteer/core";
|
|
17
|
+
import {
|
|
18
|
+
failOutput,
|
|
19
|
+
passOutput,
|
|
20
|
+
type ValidatorIssue,
|
|
21
|
+
type ValidatorOutput,
|
|
22
|
+
ValidatorOutputSchema,
|
|
23
|
+
} from "../shared/index.js";
|
|
24
|
+
|
|
25
|
+
const MANIFEST: NodeManifest = makeManifest({
|
|
26
|
+
id: "@agenteer/node-regex-check",
|
|
27
|
+
name: "regex_check",
|
|
28
|
+
description: "Assert an input satisfies a set of must-match / must-not-match regex rules.",
|
|
29
|
+
determinism: "deterministic",
|
|
30
|
+
tags: ["validator"],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const RuleSchema = z.object({
|
|
34
|
+
id: z.string().min(1),
|
|
35
|
+
pattern: z.string().min(1),
|
|
36
|
+
kind: z.enum(["must_match", "must_not_match"]),
|
|
37
|
+
/** Regex flags (e.g. `i`, `m`). Invalid flags → Failed. */
|
|
38
|
+
flags: z.string().optional(),
|
|
39
|
+
/** Human-readable message used when this rule fails. */
|
|
40
|
+
message: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
type Rule = z.infer<typeof RuleSchema>;
|
|
43
|
+
|
|
44
|
+
const InputSchema = z.object({
|
|
45
|
+
input: z.string(),
|
|
46
|
+
rules: z.array(RuleSchema).min(1),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type Input = z.input<typeof InputSchema>;
|
|
50
|
+
|
|
51
|
+
export function regexCheckFactory(): Node<Input, ValidatorOutput> {
|
|
52
|
+
return {
|
|
53
|
+
manifest: MANIFEST,
|
|
54
|
+
inputSchema: InputSchema,
|
|
55
|
+
outputSchema: ValidatorOutputSchema,
|
|
56
|
+
ctx: [],
|
|
57
|
+
model: null,
|
|
58
|
+
async execute(input: NodeInput<Input>): Promise<NodeResult<ValidatorOutput>> {
|
|
59
|
+
const { input: text, rules } = input.original;
|
|
60
|
+
|
|
61
|
+
const issues: ValidatorIssue[] = [];
|
|
62
|
+
for (const rule of rules as Rule[]) {
|
|
63
|
+
let re: RegExp;
|
|
64
|
+
try {
|
|
65
|
+
re = new RegExp(rule.pattern, rule.flags);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
kind: "failed",
|
|
69
|
+
reason: `invalid_regex:${rule.id}`,
|
|
70
|
+
retryable: false,
|
|
71
|
+
details: { error: err instanceof Error ? err.message : String(err) },
|
|
72
|
+
evidence: { verdict: "fail" },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const matched = re.test(text);
|
|
76
|
+
if (rule.kind === "must_match" && !matched) {
|
|
77
|
+
issues.push({
|
|
78
|
+
path: rule.id,
|
|
79
|
+
message: rule.message ?? `expected /${rule.pattern}/ to match`,
|
|
80
|
+
code: "must_match",
|
|
81
|
+
severity: "error",
|
|
82
|
+
});
|
|
83
|
+
} else if (rule.kind === "must_not_match" && matched) {
|
|
84
|
+
issues.push({
|
|
85
|
+
path: rule.id,
|
|
86
|
+
message: rule.message ?? `expected /${rule.pattern}/ NOT to match`,
|
|
87
|
+
code: "must_not_match",
|
|
88
|
+
severity: "error",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const out: ValidatorOutput =
|
|
94
|
+
issues.length === 0
|
|
95
|
+
? passOutput(`all ${rules.length} regex rule(s) passed`)
|
|
96
|
+
: failOutput(issues, `${issues.length} of ${rules.length} regex rule(s) failed`);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
kind: "output",
|
|
100
|
+
value: out,
|
|
101
|
+
evidence: { verdict: out.verdict },
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const regexCheckManifest = MANIFEST;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agenteer/node-test-run` — deterministic test-suite validator
|
|
3
|
+
* (sub-plan 03 §7).
|
|
4
|
+
*
|
|
5
|
+
* M4 ships a vitest adapter. Runs `vitest run --reporter=json` in the
|
|
6
|
+
* declared cwd, parses per-test results, surfaces failures as structured
|
|
7
|
+
* issues. Verdict is DATA; `Failed` is reserved for "couldn't run at all"
|
|
8
|
+
* (binary missing, denylist, capability denied).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import {
|
|
13
|
+
makeManifest,
|
|
14
|
+
type Node,
|
|
15
|
+
type NodeInput,
|
|
16
|
+
type NodeManifest,
|
|
17
|
+
type NodeResult,
|
|
18
|
+
type NodeRuntimeHandle,
|
|
19
|
+
authorizeOperation,
|
|
20
|
+
} from "@agenteer/core";
|
|
21
|
+
import {
|
|
22
|
+
failOutput,
|
|
23
|
+
passOutput,
|
|
24
|
+
runCommand,
|
|
25
|
+
tailOf,
|
|
26
|
+
ValidatorOutputSchema,
|
|
27
|
+
type ValidatorIssue,
|
|
28
|
+
type ValidatorOutput,
|
|
29
|
+
} from "../shared/index.js";
|
|
30
|
+
|
|
31
|
+
const MANIFEST: NodeManifest = makeManifest({
|
|
32
|
+
id: "@agenteer/node-test-run",
|
|
33
|
+
name: "test_run",
|
|
34
|
+
description: "Run a test suite (v1: vitest). Returns structured per-test failures.",
|
|
35
|
+
determinism: "deterministic",
|
|
36
|
+
required_actions: ["shell.exec:"],
|
|
37
|
+
dynamic_actions: true,
|
|
38
|
+
dynamic_action_spec: "fs.read:${input.cwd}",
|
|
39
|
+
tags: ["validator"],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const InputSchema = z.object({
|
|
43
|
+
framework: z.enum(["vitest"]),
|
|
44
|
+
cwd: z.string().min(1),
|
|
45
|
+
/** Optional test name or path filter. */
|
|
46
|
+
filter: z.string().optional(),
|
|
47
|
+
flags: z.array(z.string()).default([]),
|
|
48
|
+
timeout_ms: z.number().int().min(1).max(1_800_000).default(600_000),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
type Input = z.input<typeof InputSchema>;
|
|
52
|
+
|
|
53
|
+
export function testRunFactory(): Node<Input, ValidatorOutput> {
|
|
54
|
+
return {
|
|
55
|
+
manifest: MANIFEST,
|
|
56
|
+
inputSchema: InputSchema,
|
|
57
|
+
outputSchema: ValidatorOutputSchema,
|
|
58
|
+
ctx: [],
|
|
59
|
+
model: null,
|
|
60
|
+
async execute(
|
|
61
|
+
input: NodeInput<Input>,
|
|
62
|
+
handle: NodeRuntimeHandle,
|
|
63
|
+
): Promise<NodeResult<ValidatorOutput>> {
|
|
64
|
+
const { framework, cwd, filter, flags } = input.original;
|
|
65
|
+
const timeout_ms = input.original.timeout_ms ?? 600_000;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
authorizeOperation(handle.granted, { kind: "shell.exec" });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "failed",
|
|
72
|
+
reason: `permission_denied: ${err instanceof Error ? err.message : String(err)}`,
|
|
73
|
+
retryable: false,
|
|
74
|
+
evidence: { verdict: "fail" },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const adapter = ADAPTERS[framework];
|
|
79
|
+
const command = adapter.command({ ...(filter !== undefined ? { filter } : {}), flags: flags ?? [] });
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const r = await runCommand(command, { cwd, timeout_ms, signal: handle.signal });
|
|
83
|
+
const parsed = adapter.parseReport(r.stdout, r.stderr);
|
|
84
|
+
const failed = parsed.issues.filter((i) => i.severity === "error");
|
|
85
|
+
const verdict: ValidatorOutput["verdict"] =
|
|
86
|
+
r.exit_code === 0 && failed.length === 0 ? "pass" : "fail";
|
|
87
|
+
|
|
88
|
+
const out: ValidatorOutput =
|
|
89
|
+
verdict === "pass"
|
|
90
|
+
? {
|
|
91
|
+
...passOutput(`${framework} passed (${parsed.testCount} tests, ${r.duration_ms}ms)`),
|
|
92
|
+
exit_code: r.exit_code,
|
|
93
|
+
}
|
|
94
|
+
: failOutput(
|
|
95
|
+
parsed.issues.length
|
|
96
|
+
? parsed.issues
|
|
97
|
+
: [
|
|
98
|
+
{
|
|
99
|
+
message: `${framework} exited ${r.exit_code}${r.timed_out ? " (timed out)" : ""}`,
|
|
100
|
+
severity: "error",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
`${framework}: ${failed.length} test(s) failed`,
|
|
104
|
+
{
|
|
105
|
+
exit_code: r.exit_code,
|
|
106
|
+
stdout_tail: tailOf(r.stdout),
|
|
107
|
+
stderr_tail: tailOf(r.stderr),
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return { kind: "output", value: out, evidence: { verdict } };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "failed",
|
|
115
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
116
|
+
retryable: true,
|
|
117
|
+
evidence: { verdict: "fail" },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const testRunManifest = MANIFEST;
|
|
125
|
+
|
|
126
|
+
export interface TestReport {
|
|
127
|
+
testCount: number;
|
|
128
|
+
issues: ValidatorIssue[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TestAdapter {
|
|
132
|
+
readonly framework: string;
|
|
133
|
+
command(opts: { filter?: string; flags: readonly string[] }): string;
|
|
134
|
+
parseReport(stdout: string, stderr: string): TestReport;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Parse Vitest's `--reporter=json` output when it's present; fall back to stdout. */
|
|
138
|
+
const VITEST_ADAPTER: TestAdapter = {
|
|
139
|
+
framework: "vitest",
|
|
140
|
+
command(opts) {
|
|
141
|
+
const filter = opts.filter ? ` ${JSON.stringify(opts.filter)}` : "";
|
|
142
|
+
const extra = opts.flags.join(" ");
|
|
143
|
+
return `npx -y vitest run --reporter=json${filter} ${extra}`.replace(/\s+/g, " ").trim();
|
|
144
|
+
},
|
|
145
|
+
parseReport(stdout, _stderr) {
|
|
146
|
+
const json = extractLastJson(stdout);
|
|
147
|
+
if (!json) return { testCount: 0, issues: [] };
|
|
148
|
+
const issues: ValidatorIssue[] = [];
|
|
149
|
+
let testCount = 0;
|
|
150
|
+
type TestResult = {
|
|
151
|
+
name?: string;
|
|
152
|
+
fullName?: string;
|
|
153
|
+
status?: string;
|
|
154
|
+
failureMessages?: string[];
|
|
155
|
+
};
|
|
156
|
+
const testResults: Array<{ testResults?: TestResult[]; name?: string }> =
|
|
157
|
+
(json as { testResults?: Array<{ testResults?: TestResult[]; name?: string }> }).testResults ??
|
|
158
|
+
[];
|
|
159
|
+
for (const file of testResults) {
|
|
160
|
+
for (const t of file.testResults ?? []) {
|
|
161
|
+
testCount += 1;
|
|
162
|
+
if (t.status === "failed") {
|
|
163
|
+
issues.push({
|
|
164
|
+
path: `${file.name ?? "<file>"}::${t.fullName ?? t.name ?? "<test>"}`,
|
|
165
|
+
message: (t.failureMessages ?? ["failed"]).join("\n"),
|
|
166
|
+
code: "test_failed",
|
|
167
|
+
severity: "error",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { testCount, issues };
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const ADAPTERS: Record<Input["framework"], TestAdapter> = {
|
|
177
|
+
vitest: VITEST_ADAPTER,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function extractLastJson(text: string): unknown | null {
|
|
181
|
+
// Vitest prints the JSON report on its own line; a few noisy warnings
|
|
182
|
+
// can precede it. Walk backwards and take the last balanced `{...}`.
|
|
183
|
+
const idx = text.lastIndexOf("}");
|
|
184
|
+
if (idx < 0) return null;
|
|
185
|
+
let depth = 0;
|
|
186
|
+
for (let i = idx; i >= 0; i -= 1) {
|
|
187
|
+
const ch = text[i];
|
|
188
|
+
if (ch === "}") depth += 1;
|
|
189
|
+
else if (ch === "{") depth -= 1;
|
|
190
|
+
if (depth === 0) {
|
|
191
|
+
try {
|
|
192
|
+
return JSON.parse(text.slice(i, idx + 1));
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agenteer/node-typecheck` — thin wrapper over `compile` with
|
|
3
|
+
* types-only flags (sub-plan 03 §10).
|
|
4
|
+
*
|
|
5
|
+
* v1 supports TypeScript (`tsc --noEmit --strict`). The wrapper exists
|
|
6
|
+
* separately from `compile` so planners can distinguish "types only"
|
|
7
|
+
* from "full build"; the underlying adapter is shared.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import {
|
|
12
|
+
makeManifest,
|
|
13
|
+
type Node,
|
|
14
|
+
type NodeInput,
|
|
15
|
+
type NodeManifest,
|
|
16
|
+
type NodeResult,
|
|
17
|
+
type NodeRuntimeHandle,
|
|
18
|
+
authorizeOperation,
|
|
19
|
+
} from "@agenteer/core";
|
|
20
|
+
import {
|
|
21
|
+
failOutput,
|
|
22
|
+
passOutput,
|
|
23
|
+
runCommand,
|
|
24
|
+
tailOf,
|
|
25
|
+
ValidatorOutputSchema,
|
|
26
|
+
type ValidatorIssue,
|
|
27
|
+
type ValidatorOutput,
|
|
28
|
+
} from "../shared/index.js";
|
|
29
|
+
|
|
30
|
+
const MANIFEST: NodeManifest = makeManifest({
|
|
31
|
+
id: "@agenteer/node-typecheck",
|
|
32
|
+
name: "typecheck",
|
|
33
|
+
description: "Types-only check (v1: TypeScript tsc --noEmit --strict).",
|
|
34
|
+
determinism: "deterministic",
|
|
35
|
+
required_actions: ["shell.exec:"],
|
|
36
|
+
dynamic_actions: true,
|
|
37
|
+
dynamic_action_spec: "fs.read:${input.cwd}",
|
|
38
|
+
tags: ["validator"],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const InputSchema = z.object({
|
|
42
|
+
language: z.enum(["typescript"]),
|
|
43
|
+
cwd: z.string().min(1),
|
|
44
|
+
tsconfig: z.string().optional(),
|
|
45
|
+
strict: z.boolean().default(true),
|
|
46
|
+
timeout_ms: z.number().int().min(1).max(600_000).default(300_000),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type Input = z.input<typeof InputSchema>;
|
|
50
|
+
|
|
51
|
+
const TSC_ISSUE_RE = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+TS\d+:\s+(.*)$/gm;
|
|
52
|
+
|
|
53
|
+
export function typecheckFactory(): Node<Input, ValidatorOutput> {
|
|
54
|
+
return {
|
|
55
|
+
manifest: MANIFEST,
|
|
56
|
+
inputSchema: InputSchema,
|
|
57
|
+
outputSchema: ValidatorOutputSchema,
|
|
58
|
+
ctx: [],
|
|
59
|
+
model: null,
|
|
60
|
+
async execute(
|
|
61
|
+
input: NodeInput<Input>,
|
|
62
|
+
handle: NodeRuntimeHandle,
|
|
63
|
+
): Promise<NodeResult<ValidatorOutput>> {
|
|
64
|
+
const { cwd, tsconfig } = input.original;
|
|
65
|
+
const strict = input.original.strict ?? true;
|
|
66
|
+
const timeout_ms = input.original.timeout_ms ?? 300_000;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
authorizeOperation(handle.granted, { kind: "shell.exec" });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return {
|
|
72
|
+
kind: "failed",
|
|
73
|
+
reason: `permission_denied: ${err instanceof Error ? err.message : String(err)}`,
|
|
74
|
+
retryable: false,
|
|
75
|
+
evidence: { verdict: "fail" },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const tsconfigArg = tsconfig ? `--project ${JSON.stringify(tsconfig)}` : "";
|
|
80
|
+
const strictFlag = strict ? "--strict" : "";
|
|
81
|
+
const command = `npx -y tsc --noEmit ${tsconfigArg} ${strictFlag}`.replace(/\s+/g, " ").trim();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const r = await runCommand(command, { cwd, timeout_ms, signal: handle.signal });
|
|
85
|
+
const combined = `${r.stdout}\n${r.stderr}`;
|
|
86
|
+
const issues: ValidatorIssue[] = [];
|
|
87
|
+
for (const m of combined.matchAll(TSC_ISSUE_RE)) {
|
|
88
|
+
issues.push({
|
|
89
|
+
path: `${m[1]}:${m[2]}:${m[3]}`,
|
|
90
|
+
message: m[5]!.trim(),
|
|
91
|
+
code: "TS",
|
|
92
|
+
severity: m[4] === "warning" ? "warning" : "error",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const verdict: ValidatorOutput["verdict"] = r.exit_code === 0 && issues.length === 0 ? "pass" : "fail";
|
|
96
|
+
const out: ValidatorOutput =
|
|
97
|
+
verdict === "pass"
|
|
98
|
+
? { ...passOutput(`typecheck clean (${r.duration_ms}ms)`), exit_code: r.exit_code }
|
|
99
|
+
: failOutput(
|
|
100
|
+
issues.length ? issues : [{ message: `tsc exited ${r.exit_code}`, severity: "error" }],
|
|
101
|
+
`typecheck: ${issues.length} issue(s)`,
|
|
102
|
+
{
|
|
103
|
+
exit_code: r.exit_code,
|
|
104
|
+
stdout_tail: tailOf(r.stdout),
|
|
105
|
+
stderr_tail: tailOf(r.stderr),
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
return { kind: "output", value: out, evidence: { verdict } };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
kind: "failed",
|
|
112
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
113
|
+
retryable: true,
|
|
114
|
+
evidence: { verdict: "fail" },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const typecheckManifest = MANIFEST;
|