@kodr/aiwrangler 0.0.0-stage → 0.1.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/CHANGELOG.md +38 -0
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +434 -2
- package/aiwrangler.config.example.json +62 -0
- package/dist/chunk-2CIV4KGU.js +1276 -0
- package/dist/chunk-2CIV4KGU.js.map +1 -0
- package/dist/chunk-EAQASAXL.js +1252 -0
- package/dist/chunk-EAQASAXL.js.map +1 -0
- package/dist/chunk-EDVWHCQV.js +145 -0
- package/dist/chunk-EDVWHCQV.js.map +1 -0
- package/dist/chunk-NQFO47CV.js +522 -0
- package/dist/chunk-NQFO47CV.js.map +1 -0
- package/dist/cli.js +262 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +697 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.js +214 -0
- package/dist/mcp.js.map +1 -0
- package/dist/plugin.js +112 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tools.js +127 -0
- package/dist/tools.js.map +1 -0
- package/package.json +87 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authoritativeFor,
|
|
3
|
+
effectiveTruth,
|
|
4
|
+
formatReport,
|
|
5
|
+
paperSectionForSpec,
|
|
6
|
+
runCheck,
|
|
7
|
+
runPlanGate,
|
|
8
|
+
sectionById,
|
|
9
|
+
sectionsOfKind
|
|
10
|
+
} from "./chunk-2CIV4KGU.js";
|
|
11
|
+
import {
|
|
12
|
+
ConfigSchema,
|
|
13
|
+
PRECEDENCE,
|
|
14
|
+
activeThresholds,
|
|
15
|
+
applyEnvOverrides,
|
|
16
|
+
applyGateFlag,
|
|
17
|
+
checkCommitMessage,
|
|
18
|
+
checkRepoDiff,
|
|
19
|
+
ensurePaperText,
|
|
20
|
+
expandPath,
|
|
21
|
+
findConfigFile,
|
|
22
|
+
flattenGates,
|
|
23
|
+
loadConfig,
|
|
24
|
+
loadCorpus,
|
|
25
|
+
loadCorpusFrom,
|
|
26
|
+
parseDivergences,
|
|
27
|
+
parseDoc,
|
|
28
|
+
parseJsonc,
|
|
29
|
+
parsePaper,
|
|
30
|
+
parsePlan,
|
|
31
|
+
parseQuestions,
|
|
32
|
+
parseRules,
|
|
33
|
+
parseSpec,
|
|
34
|
+
sha256Hex,
|
|
35
|
+
shortHash,
|
|
36
|
+
slug,
|
|
37
|
+
splitMarkdown,
|
|
38
|
+
stripJsonc
|
|
39
|
+
} from "./chunk-EAQASAXL.js";
|
|
40
|
+
export {
|
|
41
|
+
ConfigSchema,
|
|
42
|
+
PRECEDENCE,
|
|
43
|
+
activeThresholds,
|
|
44
|
+
applyEnvOverrides,
|
|
45
|
+
applyGateFlag,
|
|
46
|
+
authoritativeFor,
|
|
47
|
+
checkCommitMessage,
|
|
48
|
+
checkRepoDiff,
|
|
49
|
+
effectiveTruth,
|
|
50
|
+
ensurePaperText,
|
|
51
|
+
expandPath,
|
|
52
|
+
findConfigFile,
|
|
53
|
+
flattenGates,
|
|
54
|
+
formatReport,
|
|
55
|
+
loadConfig,
|
|
56
|
+
loadCorpus,
|
|
57
|
+
loadCorpusFrom,
|
|
58
|
+
paperSectionForSpec,
|
|
59
|
+
parseDivergences,
|
|
60
|
+
parseDoc,
|
|
61
|
+
parseJsonc,
|
|
62
|
+
parsePaper,
|
|
63
|
+
parsePlan,
|
|
64
|
+
parseQuestions,
|
|
65
|
+
parseRules,
|
|
66
|
+
parseSpec,
|
|
67
|
+
runCheck,
|
|
68
|
+
runPlanGate,
|
|
69
|
+
sectionById,
|
|
70
|
+
sectionsOfKind,
|
|
71
|
+
sha256Hex,
|
|
72
|
+
shortHash,
|
|
73
|
+
slug,
|
|
74
|
+
splitMarkdown,
|
|
75
|
+
stripJsonc
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkGate,
|
|
4
|
+
configReport,
|
|
5
|
+
isAckWriteAttempt,
|
|
6
|
+
isOverrideWriteAttempt,
|
|
7
|
+
listPendingAck,
|
|
8
|
+
loopResetGate,
|
|
9
|
+
planGate,
|
|
10
|
+
reportOverrides,
|
|
11
|
+
requestHumanAck,
|
|
12
|
+
requestHumanOverride,
|
|
13
|
+
resolveGateDir
|
|
14
|
+
} from "./chunk-NQFO47CV.js";
|
|
15
|
+
import "./chunk-2CIV4KGU.js";
|
|
16
|
+
import "./chunk-EDVWHCQV.js";
|
|
17
|
+
import "./chunk-EAQASAXL.js";
|
|
18
|
+
|
|
19
|
+
// src/mcp.ts
|
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
|
|
24
|
+
// src/version.ts
|
|
25
|
+
import { createRequire } from "module";
|
|
26
|
+
var require2 = createRequire(import.meta.url);
|
|
27
|
+
function packageVersion() {
|
|
28
|
+
const pkg = require2("../package.json");
|
|
29
|
+
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
30
|
+
throw new Error("aiwrangler: package.json has no version");
|
|
31
|
+
}
|
|
32
|
+
return pkg.version;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/mcp.ts
|
|
36
|
+
var rootArg = process.argv.slice(2).find((a) => !a.startsWith("-"));
|
|
37
|
+
if (process.env.AIWRANGLER_DIR === void 0 && rootArg !== void 0) {
|
|
38
|
+
process.env.AIWRANGLER_DIR = rootArg;
|
|
39
|
+
}
|
|
40
|
+
var server = new McpServer(
|
|
41
|
+
{ name: "aiwrangler", version: packageVersion() },
|
|
42
|
+
{ capabilities: { logging: {} } }
|
|
43
|
+
);
|
|
44
|
+
var dirArg = z.string().optional().describe(
|
|
45
|
+
"absolute path of the workspace/repo root to operate on (config discovery walks up from here). Defaults to the server's startup root. Pass the workspace root whenever the server was started elsewhere."
|
|
46
|
+
);
|
|
47
|
+
server.registerTool(
|
|
48
|
+
"wrangler_config",
|
|
49
|
+
{
|
|
50
|
+
description: "AiWrangler ground-truth and gate status. Prints the effective configuration (including AIWRANGLER_* env overrides), every gate flag, the active policy thresholds, and the loaded corpus summary (spec sections, review rules, divergence rulings, open questions, one-way doors, whitepaper sections, corpus hash). Use this first in any session to confirm what AiWrangler is enforcing and that the corpus is present.",
|
|
51
|
+
inputSchema: { dir: dirArg }
|
|
52
|
+
},
|
|
53
|
+
async ({ dir }) => ({
|
|
54
|
+
content: [{ type: "text", text: safe(() => configReport(resolveGateDir(dir))) }]
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
server.registerTool(
|
|
58
|
+
"wrangler_plan",
|
|
59
|
+
{
|
|
60
|
+
description: "AiWrangler pre-flight plan gate. Call BEFORE writing any code. Judges the task against the ground-truth corpus (protocol spec, review rules, divergence rulings, open questions, one-way doors) and returns: an ACTION (pass | clarify | human), the reasons, and a ground-truth context pack you MUST honor while implementing. clarify means the task is ambiguous or touches an unresolved question: ask the user before coding. human means a one-way door or steering risk: stop for the owner.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
task: z.string().describe("the task as given, verbatim if possible"),
|
|
63
|
+
plan: z.string().optional().describe("your implementation plan, if you have one drafted"),
|
|
64
|
+
dir: dirArg
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
async ({ task, plan, dir }) => ({
|
|
68
|
+
content: [{ type: "text", text: await planGate(resolveGateDir(dir), { task, ...plan ? { plan } : {} }) }]
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
server.registerTool(
|
|
72
|
+
"wrangler_check",
|
|
73
|
+
{
|
|
74
|
+
description: "AiWrangler post-generation gate. Call BEFORE declaring a task done (and after each substantive revision). Runs deterministic checks (review rules, em dashes, deferred-work discipline, pub-API surface, test discipline) plus a Jev semantic battery (claims vs diff, scope discipline, silent defaults, comment truthfulness, spec contradictions, self-report verification) against the dirty files across all tracked repos. Returns PASS / FIX REQUIRED (with a cited, actionable fix list) / HUMAN REVIEW / DEGRADED. Fix every cited finding and re-run until PASS. Pass agent_summary and tool_output to have your status claims verified too.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
task: z.string().optional().describe("the task being implemented (improves scope and spec checks)"),
|
|
77
|
+
agent_summary: z.string().optional().describe("your summary of what you did and its status (e.g. 'tests pass, lint clean')"),
|
|
78
|
+
tool_output: z.string().optional().describe("the raw output of the build/test commands you ran, for self-report verification"),
|
|
79
|
+
cargo: z.boolean().optional().describe("also run cargo check on dirty repos"),
|
|
80
|
+
tests: z.boolean().optional().describe("also run cargo test on dirty repos (implies cargo)"),
|
|
81
|
+
dir: dirArg
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async ({ task, agent_summary, tool_output, cargo, tests, dir }) => ({
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: await checkGate(resolveGateDir(dir), {
|
|
89
|
+
...task ? { task } : {},
|
|
90
|
+
...agent_summary ? { agentSummary: agent_summary } : {},
|
|
91
|
+
...tool_output ? { toolOutput: tool_output } : {},
|
|
92
|
+
cargo: cargo ?? false,
|
|
93
|
+
tests: tests ?? false
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
function safe(fn) {
|
|
100
|
+
try {
|
|
101
|
+
return fn();
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return `error: ${err instanceof Error ? err.message : String(err)}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
var decisionSchema = {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
decision: {
|
|
110
|
+
type: "string",
|
|
111
|
+
title: "Decision",
|
|
112
|
+
description: "Approve records this write. Deny leaves the gate unchanged.",
|
|
113
|
+
enum: ["approve", "deny"],
|
|
114
|
+
enumNames: ["Approve", "Deny"]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: ["decision"]
|
|
118
|
+
};
|
|
119
|
+
async function elicitDecision(summary) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await server.server.elicitInput({
|
|
122
|
+
mode: "form",
|
|
123
|
+
message: summary,
|
|
124
|
+
requestedSchema: decisionSchema
|
|
125
|
+
});
|
|
126
|
+
if (result.action === "accept" && result.content?.decision === "approve") return "approve";
|
|
127
|
+
return "deny";
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
130
|
+
if (message.toLowerCase().includes("does not support") && message.toLowerCase().includes("elicit")) return "unsupported";
|
|
131
|
+
return { error: message };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function toolResult(text) {
|
|
135
|
+
return { ...text.startsWith("aiwrangler:") ? { isError: true } : {}, content: [{ type: "text", text }] };
|
|
136
|
+
}
|
|
137
|
+
server.registerTool(
|
|
138
|
+
"wrangler_ack",
|
|
139
|
+
{
|
|
140
|
+
description: "AiWrangler HUMAN REVIEW. With no rules, note, or diffHash, lists pending human-severity findings and the diff hash. Passing rules requests an acknowledgment. Those arguments do not approve it: the server asks the human with an MCP elicitation prompt (findings, rules, diff hash, and note), or a desktop dialog if this client has no elicitation. Nothing is recorded unless the human approves. Block-severity findings cannot be acked. A diff change voids the ack.",
|
|
141
|
+
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
142
|
+
inputSchema: {
|
|
143
|
+
rules: z.array(z.string()).optional().describe('rule IDs to ask the owner to acknowledge, e.g. ["RULE-10"] or ["*"]. Does not approve by itself.'),
|
|
144
|
+
note: z.string().optional().describe("shown to the owner on the approval prompt and stored if they approve"),
|
|
145
|
+
diffHash: z.string().optional().describe("diff hash from the check report; defaults to the latest recorded gate run. Does not approve by itself."),
|
|
146
|
+
dir: dirArg
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
async ({ rules, note, diffHash, dir }) => {
|
|
150
|
+
if (!isAckWriteAttempt({ rules, note, diffHash })) {
|
|
151
|
+
return { content: [{ type: "text", text: safe(() => listPendingAck(resolveGateDir(dir))) }] };
|
|
152
|
+
}
|
|
153
|
+
const text = await requestHumanAck(
|
|
154
|
+
resolveGateDir(dir),
|
|
155
|
+
{ rules: rules ?? [], ...note !== void 0 ? { note } : {}, ...diffHash !== void 0 ? { diffHash } : {} },
|
|
156
|
+
{ by: "mcp", elicit: elicitDecision }
|
|
157
|
+
);
|
|
158
|
+
return toolResult(text);
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
async function main() {
|
|
162
|
+
const transport = new StdioServerTransport();
|
|
163
|
+
await server.connect(transport);
|
|
164
|
+
console.error(`aiwrangler-mcp: ready (root: ${resolveGateDir()})`);
|
|
165
|
+
}
|
|
166
|
+
server.registerTool(
|
|
167
|
+
"wrangler_override",
|
|
168
|
+
{
|
|
169
|
+
description: "AiWrangler runtime overrides. With no set, clear, or note, reports active overrides. Passing set or clear requests a change. Those arguments do not apply it: the server asks the human with an MCP elicitation prompt, or a desktop dialog if this client has no elicitation. Nothing changes unless the human approves. Overrides expire on the next PASS.",
|
|
170
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
171
|
+
inputSchema: {
|
|
172
|
+
set: z.object({
|
|
173
|
+
activePolicy: z.string().optional(),
|
|
174
|
+
egressSemantic: z.boolean().optional(),
|
|
175
|
+
actionThreshold: z.number().min(0).max(1).optional(),
|
|
176
|
+
reviewThreshold: z.number().min(0).max(1).optional(),
|
|
177
|
+
gates: z.record(z.string(), z.boolean()).optional()
|
|
178
|
+
}).optional().describe("overrides to ask the owner to apply. Does not apply them by itself."),
|
|
179
|
+
clear: z.boolean().optional().describe("ask the owner to remove active overrides. Does not clear them by itself."),
|
|
180
|
+
note: z.string().optional().describe("shown to the owner on the approval prompt and stored if they approve"),
|
|
181
|
+
dir: dirArg
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
async ({ set, clear, note, dir }) => {
|
|
185
|
+
if (!isOverrideWriteAttempt({ set, clear, note })) {
|
|
186
|
+
return { content: [{ type: "text", text: safe(() => reportOverrides(resolveGateDir(dir))) }] };
|
|
187
|
+
}
|
|
188
|
+
const text = await requestHumanOverride(
|
|
189
|
+
resolveGateDir(dir),
|
|
190
|
+
{
|
|
191
|
+
...set !== void 0 ? { set } : {},
|
|
192
|
+
...clear === true ? { clear: true } : {},
|
|
193
|
+
...note !== void 0 ? { note } : {}
|
|
194
|
+
},
|
|
195
|
+
{ by: "mcp", elicit: elicitDecision }
|
|
196
|
+
);
|
|
197
|
+
return toolResult(text);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
server.registerTool(
|
|
201
|
+
"wrangler_loop_reset",
|
|
202
|
+
{
|
|
203
|
+
description: "AiWrangler circuit-breaker reset. Call ONLY after the user has decided the repeated findings are not making progress: it clears the retry loop state so the next check starts fresh, and audits a notice. No-op when there is no loop state.",
|
|
204
|
+
inputSchema: { dir: dirArg }
|
|
205
|
+
},
|
|
206
|
+
async ({ dir }) => ({
|
|
207
|
+
content: [{ type: "text", text: safe(() => loopResetGate(resolveGateDir(dir), "mcp")) }]
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
main().catch((err) => {
|
|
211
|
+
console.error("aiwrangler-mcp: fatal:", err);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
});
|
|
214
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp.ts","../src/version.ts"],"sourcesContent":["#!/usr/bin/env node\n// AiWrangler MCP server. Ack and override writes ask the human through MCP\n// elicitation (the client UI). If the client has no form elicitation, the\n// server falls back to a native desktop dialog. Tool arguments never authorize\n// a write by themselves. stdio transport.\n//\n// node dist/mcp.js [rootDir] # rootDir = default gate directory\n// AIWRANGLER_DIR=/path node dist/mcp.js\n//\n// Per-call `dir` arguments override the startup root, so one server can\n// serve multiple workspaces.\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport { z } from \"zod\"\nimport type { ElicitOutcome } from \"./approve.js\"\nimport {\n configReport,\n planGate,\n checkGate,\n isAckWriteAttempt,\n isOverrideWriteAttempt,\n listPendingAck,\n reportOverrides,\n requestHumanAck,\n requestHumanOverride,\n loopResetGate,\n resolveGateDir,\n} from \"./core.js\"\nimport type { RuntimeOverrides } from \"./overrides.js\"\nimport { packageVersion } from \"./version.js\"\n\nconst rootArg = process.argv.slice(2).find((a) => !a.startsWith(\"-\"))\nif (process.env.AIWRANGLER_DIR === undefined && rootArg !== undefined) {\n process.env.AIWRANGLER_DIR = rootArg\n}\n\nconst server = new McpServer(\n { name: \"aiwrangler\", version: packageVersion() },\n { capabilities: { logging: {} } },\n)\n\nconst dirArg = z\n .string()\n .optional()\n .describe(\n \"absolute path of the workspace/repo root to operate on (config discovery walks up from here). \" +\n \"Defaults to the server's startup root. Pass the workspace root whenever the server was started elsewhere.\",\n )\n\nserver.registerTool(\n \"wrangler_config\",\n {\n description:\n \"AiWrangler ground-truth and gate status. Prints the effective configuration (including AIWRANGLER_* env overrides), every gate flag, the active policy thresholds, and the loaded corpus summary (spec sections, review rules, divergence rulings, open questions, one-way doors, whitepaper sections, corpus hash). Use this first in any session to confirm what AiWrangler is enforcing and that the corpus is present.\",\n inputSchema: { dir: dirArg },\n },\n async ({ dir }) => ({\n content: [{ type: \"text\", text: safe(() => configReport(resolveGateDir(dir))) }],\n }),\n)\n\nserver.registerTool(\n \"wrangler_plan\",\n {\n description:\n \"AiWrangler pre-flight plan gate. Call BEFORE writing any code. Judges the task against the ground-truth corpus (protocol spec, review rules, divergence rulings, open questions, one-way doors) and returns: an ACTION (pass | clarify | human), the reasons, and a ground-truth context pack you MUST honor while implementing. clarify means the task is ambiguous or touches an unresolved question: ask the user before coding. human means a one-way door or steering risk: stop for the owner.\",\n inputSchema: {\n task: z.string().describe(\"the task as given, verbatim if possible\"),\n plan: z.string().optional().describe(\"your implementation plan, if you have one drafted\"),\n dir: dirArg,\n },\n },\n async ({ task, plan, dir }) => ({\n content: [{ type: \"text\", text: await planGate(resolveGateDir(dir), { task, ...(plan ? { plan } : {}) }) }],\n }),\n)\n\nserver.registerTool(\n \"wrangler_check\",\n {\n description:\n \"AiWrangler post-generation gate. Call BEFORE declaring a task done (and after each substantive revision). Runs deterministic checks (review rules, em dashes, deferred-work discipline, pub-API surface, test discipline) plus a Jev semantic battery (claims vs diff, scope discipline, silent defaults, comment truthfulness, spec contradictions, self-report verification) against the dirty files across all tracked repos. Returns PASS / FIX REQUIRED (with a cited, actionable fix list) / HUMAN REVIEW / DEGRADED. Fix every cited finding and re-run until PASS. Pass agent_summary and tool_output to have your status claims verified too.\",\n inputSchema: {\n task: z.string().optional().describe(\"the task being implemented (improves scope and spec checks)\"),\n agent_summary: z.string().optional().describe(\"your summary of what you did and its status (e.g. 'tests pass, lint clean')\"),\n tool_output: z.string().optional().describe(\"the raw output of the build/test commands you ran, for self-report verification\"),\n cargo: z.boolean().optional().describe(\"also run cargo check on dirty repos\"),\n tests: z.boolean().optional().describe(\"also run cargo test on dirty repos (implies cargo)\"),\n dir: dirArg,\n },\n },\n async ({ task, agent_summary, tool_output, cargo, tests, dir }) => ({\n content: [\n {\n type: \"text\",\n text: await checkGate(resolveGateDir(dir), {\n ...(task ? { task } : {}),\n ...(agent_summary ? { agentSummary: agent_summary } : {}),\n ...(tool_output ? { toolOutput: tool_output } : {}),\n cargo: cargo ?? false,\n tests: tests ?? false,\n }),\n },\n ],\n }),\n)\n\nfunction safe(fn: () => string): string {\n try {\n return fn()\n } catch (err) {\n return `error: ${err instanceof Error ? err.message : String(err)}`\n }\n}\n\nconst decisionSchema = {\n type: \"object\" as const,\n properties: {\n decision: {\n type: \"string\" as const,\n title: \"Decision\",\n description: \"Approve records this write. Deny leaves the gate unchanged.\",\n enum: [\"approve\", \"deny\"],\n enumNames: [\"Approve\", \"Deny\"],\n },\n },\n required: [\"decision\"],\n}\n\n/** Ask the MCP client UI. Unsupported clients fall through to the OS dialog. */\nasync function elicitDecision(summary: string): Promise<ElicitOutcome> {\n try {\n const result = await server.server.elicitInput({\n mode: \"form\",\n message: summary,\n requestedSchema: decisionSchema,\n })\n if (result.action === \"accept\" && result.content?.decision === \"approve\") return \"approve\"\n return \"deny\"\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (message.toLowerCase().includes(\"does not support\") && message.toLowerCase().includes(\"elicit\")) return \"unsupported\"\n return { error: message }\n }\n}\n\nfunction toolResult(text: string): { isError?: boolean; content: { type: \"text\"; text: string }[] } {\n return { ...(text.startsWith(\"aiwrangler:\") ? { isError: true } : {}), content: [{ type: \"text\", text }] }\n}\n\nserver.registerTool(\n \"wrangler_ack\",\n {\n description:\n \"AiWrangler HUMAN REVIEW. With no rules, note, or diffHash, lists pending human-severity findings and the diff hash. Passing rules requests an acknowledgment. Those arguments do not approve it: the server asks the human with an MCP elicitation prompt (findings, rules, diff hash, and note), or a desktop dialog if this client has no elicitation. Nothing is recorded unless the human approves. Block-severity findings cannot be acked. A diff change voids the ack.\",\n annotations: { readOnlyHint: false, destructiveHint: false },\n inputSchema: {\n rules: z.array(z.string()).optional().describe('rule IDs to ask the owner to acknowledge, e.g. [\"RULE-10\"] or [\"*\"]. Does not approve by itself.'),\n note: z.string().optional().describe(\"shown to the owner on the approval prompt and stored if they approve\"),\n diffHash: z.string().optional().describe(\"diff hash from the check report; defaults to the latest recorded gate run. Does not approve by itself.\"),\n dir: dirArg,\n },\n },\n async ({ rules, note, diffHash, dir }) => {\n if (!isAckWriteAttempt({ rules, note, diffHash })) {\n return { content: [{ type: \"text\" as const, text: safe(() => listPendingAck(resolveGateDir(dir))) }] }\n }\n const text = await requestHumanAck(\n resolveGateDir(dir),\n { rules: rules ?? [], ...(note !== undefined ? { note } : {}), ...(diffHash !== undefined ? { diffHash } : {}) },\n { by: \"mcp\", elicit: elicitDecision },\n )\n return toolResult(text)\n },\n)\n\nasync function main() {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n console.error(`aiwrangler-mcp: ready (root: ${resolveGateDir()})`)\n}\n\nserver.registerTool(\n \"wrangler_override\",\n {\n description:\n \"AiWrangler runtime overrides. With no set, clear, or note, reports active overrides. Passing set or clear requests a change. Those arguments do not apply it: the server asks the human with an MCP elicitation prompt, or a desktop dialog if this client has no elicitation. Nothing changes unless the human approves. Overrides expire on the next PASS.\",\n annotations: { readOnlyHint: false, destructiveHint: true },\n inputSchema: {\n set: z\n .object({\n activePolicy: z.string().optional(),\n egressSemantic: z.boolean().optional(),\n actionThreshold: z.number().min(0).max(1).optional(),\n reviewThreshold: z.number().min(0).max(1).optional(),\n gates: z.record(z.string(), z.boolean()).optional(),\n })\n .optional()\n .describe(\"overrides to ask the owner to apply. Does not apply them by itself.\"),\n clear: z.boolean().optional().describe(\"ask the owner to remove active overrides. Does not clear them by itself.\"),\n note: z.string().optional().describe(\"shown to the owner on the approval prompt and stored if they approve\"),\n dir: dirArg,\n },\n },\n async ({ set, clear, note, dir }) => {\n if (!isOverrideWriteAttempt({ set, clear, note })) {\n return { content: [{ type: \"text\" as const, text: safe(() => reportOverrides(resolveGateDir(dir))) }] }\n }\n const text = await requestHumanOverride(\n resolveGateDir(dir),\n {\n ...(set !== undefined ? { set: set as RuntimeOverrides } : {}),\n ...(clear === true ? { clear: true } : {}),\n ...(note !== undefined ? { note } : {}),\n },\n { by: \"mcp\", elicit: elicitDecision },\n )\n return toolResult(text)\n },\n)\n\nserver.registerTool(\n \"wrangler_loop_reset\",\n {\n description:\n \"AiWrangler circuit-breaker reset. Call ONLY after the user has decided the repeated findings are not making progress: it clears the retry loop state so the next check starts fresh, and audits a notice. No-op when there is no loop state.\",\n inputSchema: { dir: dirArg },\n },\n async ({ dir }) => ({\n content: [{ type: \"text\", text: safe(() => loopResetGate(resolveGateDir(dir), \"mcp\")) }],\n }),\n)\n\nmain().catch((err) => {\n console.error(\"aiwrangler-mcp: fatal:\", err)\n process.exit(1)\n})\n","import { createRequire } from \"node:module\"\n\nconst require = createRequire(import.meta.url)\n\n/** Version from the nearest package.json, so the published server matches the npm package. */\nexport function packageVersion(): string {\n const pkg = require(\"../package.json\") as { version?: unknown }\n if (typeof pkg.version !== \"string\" || pkg.version.length === 0) {\n throw new Error(\"aiwrangler: package.json has no version\")\n }\n return pkg.version\n}\n"],"mappings":"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","names":["require"]}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
setJevWarnSink
|
|
3
|
+
} from "./chunk-EDVWHCQV.js";
|
|
4
|
+
import {
|
|
5
|
+
Ledger,
|
|
6
|
+
diffFingerprint,
|
|
7
|
+
findConfigFile,
|
|
8
|
+
formatFindings,
|
|
9
|
+
loadConfig,
|
|
10
|
+
runDeterministic
|
|
11
|
+
} from "./chunk-EAQASAXL.js";
|
|
12
|
+
|
|
13
|
+
// src/plugin.ts
|
|
14
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs";
|
|
15
|
+
import { join, dirname } from "path";
|
|
16
|
+
var WranglerWatchPlugin = async ({ client, directory }) => {
|
|
17
|
+
const log = (level, message) => client.app.log({ body: { service: "aiwrangler-watch", level, message } }).catch(() => {
|
|
18
|
+
});
|
|
19
|
+
if (!directory || !findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
let loaded;
|
|
23
|
+
try {
|
|
24
|
+
loaded = loadConfig(directory);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
void log("error", `aiwrangler config failed to load; watch plugin inactive: ${err instanceof Error ? err.message : String(err)}`);
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
const statePath = join(loaded.root, ".aiwrangler", "watch-state.json");
|
|
30
|
+
setJevWarnSink((message) => {
|
|
31
|
+
void log("warn", message);
|
|
32
|
+
client.tui.showToast({ body: { title: "aiwrangler", message, variant: "warning", duration: 8e3 } }).catch(() => {
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
new Ledger(loaded.config, loaded.root).append({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "notice", message });
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
let watch = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(statePath)) watch = { ...watch, ...JSON.parse(readFileSync(statePath, "utf8")) };
|
|
42
|
+
} catch {
|
|
43
|
+
watch = { lastFingerprint: "", nudges: 0, greenAnnounced: false };
|
|
44
|
+
}
|
|
45
|
+
const save = () => {
|
|
46
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
47
|
+
writeFileSync(statePath, JSON.stringify(watch), "utf8");
|
|
48
|
+
};
|
|
49
|
+
async function nudge(sessionID, text) {
|
|
50
|
+
try {
|
|
51
|
+
await client.session.promptAsync({
|
|
52
|
+
path: { id: sessionID },
|
|
53
|
+
body: { parts: [{ type: "text", text }] }
|
|
54
|
+
});
|
|
55
|
+
} catch (err) {
|
|
56
|
+
void log("warn", `nudge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
event: async ({ event }) => {
|
|
61
|
+
const e = event;
|
|
62
|
+
if (e.type !== "session.idle") return;
|
|
63
|
+
const props = e.properties ?? {};
|
|
64
|
+
const sessionID = String(props.sessionID ?? props.id ?? "");
|
|
65
|
+
if (!sessionID) return;
|
|
66
|
+
try {
|
|
67
|
+
const loaded2 = loadConfig(directory);
|
|
68
|
+
if (!loaded2.config.gates.session.nudge) return;
|
|
69
|
+
const report = await runDeterministic(loaded2.config, loaded2.root, { cargo: false });
|
|
70
|
+
const fingerprint = diffFingerprint(
|
|
71
|
+
report.repoDiffs.flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length))).join("|")
|
|
72
|
+
);
|
|
73
|
+
if (fingerprint === watch.lastFingerprint) return;
|
|
74
|
+
watch.lastFingerprint = fingerprint;
|
|
75
|
+
const blocking = report.findings.filter((f) => f.severity === "block" || f.severity === "human");
|
|
76
|
+
if (blocking.length > 0 && watch.nudges < loaded2.config.gates.session.maxNudges) {
|
|
77
|
+
watch.nudges++;
|
|
78
|
+
watch.greenAnnounced = false;
|
|
79
|
+
save();
|
|
80
|
+
await nudge(
|
|
81
|
+
sessionID,
|
|
82
|
+
[
|
|
83
|
+
"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:",
|
|
84
|
+
"",
|
|
85
|
+
formatFindings(blocking)
|
|
86
|
+
].join("\n")
|
|
87
|
+
);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {
|
|
91
|
+
watch.greenAnnounced = true;
|
|
92
|
+
save();
|
|
93
|
+
await nudge(
|
|
94
|
+
sessionID,
|
|
95
|
+
"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done."
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (report.repoDiffs.length === 0) {
|
|
99
|
+
watch.nudges = 0;
|
|
100
|
+
watch.greenAnnounced = false;
|
|
101
|
+
save();
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
void log("warn", `idle check error: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
export {
|
|
110
|
+
WranglerWatchPlugin
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Plugin } from \"@opencode-ai/plugin\"\nimport { loadConfig, findConfigFile } from \"./config.js\"\nimport { loadCorpus } from \"./groundtruth/corpus.js\"\nimport { runDeterministic, formatFindings } from \"./gates/deterministic/index.js\"\nimport { diffFingerprint } from \"./loop.js\"\nimport { setJevWarnSink } from \"./jev/client.js\"\nimport { Ledger } from \"./ledger.js\"\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from \"node:fs\"\nimport { join, dirname } from \"node:path\"\n\ninterface WatchState {\n lastFingerprint: string\n nudges: number\n greenAnnounced: boolean\n}\n\nexport const WranglerWatchPlugin: Plugin = async ({ client, directory }) => {\n const log = (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string) =>\n client.app.log({ body: { service: \"aiwrangler-watch\", level, message } }).catch(() => {})\n\n if (!directory || (!findConfigFile(directory) && !process.env.AIWRANGLER_CONFIG)) {\n return {}\n }\n\n let loaded\n try {\n loaded = loadConfig(directory)\n } catch (err) {\n void log(\"error\", `aiwrangler config failed to load; watch plugin inactive: ${err instanceof Error ? err.message : String(err)}`)\n return {}\n }\n const statePath = join(loaded.root, \".aiwrangler\", \"watch-state.json\")\n\n setJevWarnSink((message) => {\n void log(\"warn\", message)\n client.tui\n .showToast({ body: { title: \"aiwrangler\", message, variant: \"warning\", duration: 8000 } })\n .catch(() => {})\n try {\n new Ledger(loaded.config, loaded.root).append({ ts: new Date().toISOString(), kind: \"notice\", message })\n } catch {\n /* ledger unavailable; the app log above still captured it */\n }\n })\n let watch: WatchState = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n try {\n if (existsSync(statePath)) watch = { ...watch, ...(JSON.parse(readFileSync(statePath, \"utf8\")) as WatchState) }\n } catch {\n watch = { lastFingerprint: \"\", nudges: 0, greenAnnounced: false }\n }\n const save = () => {\n mkdirSync(dirname(statePath), { recursive: true })\n writeFileSync(statePath, JSON.stringify(watch), \"utf8\")\n }\n\n async function nudge(sessionID: string, text: string) {\n try {\n await client.session.promptAsync({\n path: { id: sessionID },\n body: { parts: [{ type: \"text\", text }] },\n })\n } catch (err) {\n void log(\"warn\", `nudge failed: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n return {\n event: async ({ event }) => {\n const e = event as { type?: string; properties?: Record<string, any> }\n if (e.type !== \"session.idle\") return\n const props = e.properties ?? {}\n const sessionID = String(props.sessionID ?? props.id ?? \"\")\n if (!sessionID) return\n\n try {\n const loaded = loadConfig(directory!)\n if (!loaded.config.gates.session.nudge) return\n const report = await runDeterministic(loaded.config, loaded.root, { cargo: false })\n const fingerprint = diffFingerprint(\n report.repoDiffs\n .flatMap((d) => d.files.map((f) => f.path + String(f.hunks.length)))\n .join(\"|\"),\n )\n if (fingerprint === watch.lastFingerprint) return\n watch.lastFingerprint = fingerprint\n\n const blocking = report.findings.filter((f) => f.severity === \"block\" || f.severity === \"human\")\n if (blocking.length > 0 && watch.nudges < loaded.config.gates.session.maxNudges) {\n watch.nudges++\n watch.greenAnnounced = false\n save()\n await nudge(\n sessionID,\n [\n \"aiwrangler gate: deterministic findings on the current dirty files. Fix these before finishing, then run the wrangler_check tool for the full battery:\",\n \"\",\n formatFindings(blocking),\n ].join(\"\\n\"),\n )\n return\n }\n\n if (report.repoDiffs.length > 0 && blocking.length === 0 && !watch.greenAnnounced && watch.nudges > 0) {\n watch.greenAnnounced = true\n save()\n await nudge(\n sessionID,\n \"aiwrangler gate: previous deterministic findings are resolved. Run the wrangler_check tool for the full semantic battery before declaring done.\",\n )\n }\n if (report.repoDiffs.length === 0) {\n watch.nudges = 0\n watch.greenAnnounced = false\n save()\n }\n } catch (err) {\n void log(\"warn\", `idle check error: ${err instanceof Error ? err.message : String(err)}`)\n }\n },\n }\n}\n"],"mappings":"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","names":["loaded"]}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkGate,
|
|
3
|
+
configReport,
|
|
4
|
+
isAckWriteAttempt,
|
|
5
|
+
isOverrideWriteAttempt,
|
|
6
|
+
listPendingAck,
|
|
7
|
+
loopResetGate,
|
|
8
|
+
planGate,
|
|
9
|
+
reportOverrides,
|
|
10
|
+
requestHumanAck,
|
|
11
|
+
requestHumanOverride
|
|
12
|
+
} from "./chunk-NQFO47CV.js";
|
|
13
|
+
import "./chunk-2CIV4KGU.js";
|
|
14
|
+
import "./chunk-EDVWHCQV.js";
|
|
15
|
+
import "./chunk-EAQASAXL.js";
|
|
16
|
+
|
|
17
|
+
// src/tools.ts
|
|
18
|
+
import { tool } from "@opencode-ai/plugin";
|
|
19
|
+
var wrangler_config = tool({
|
|
20
|
+
description: "AiWrangler ground-truth and gate status. Prints the effective configuration (including AIWRANGLER_* env overrides), every gate flag, the active policy thresholds, and the loaded corpus summary (spec sections, review rules, divergence rulings, open questions, one-way doors, whitepaper sections, corpus hash). Use this first in any session to confirm what AiWrangler is enforcing and that the corpus is present.",
|
|
21
|
+
args: {},
|
|
22
|
+
async execute(_args, ctx) {
|
|
23
|
+
const dir = ctx.directory ?? process.cwd();
|
|
24
|
+
try {
|
|
25
|
+
return configReport(dir);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return `error: ${err instanceof Error ? err.message : String(err)}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var wrangler_plan = tool({
|
|
32
|
+
description: "AiWrangler pre-flight plan gate. Call BEFORE writing any code. Judges the task against the ground-truth corpus (protocol spec, review rules, divergence rulings, open questions, one-way doors) and returns: an ACTION (pass | clarify | human), the reasons, and a ground-truth context pack you MUST honor while implementing. clarify means the task is ambiguous or touches an unresolved question: ask the user before coding. human means a one-way door or steering risk: stop for the owner.",
|
|
33
|
+
args: {
|
|
34
|
+
task: tool.schema.string().describe("the task as given, verbatim if possible"),
|
|
35
|
+
plan: tool.schema.string().optional().describe("your implementation plan, if you have one drafted")
|
|
36
|
+
},
|
|
37
|
+
async execute(args, ctx) {
|
|
38
|
+
const dir = ctx.directory ?? process.cwd();
|
|
39
|
+
return planGate(dir, {
|
|
40
|
+
task: args.task,
|
|
41
|
+
...args.plan ? { plan: args.plan } : {},
|
|
42
|
+
...ctx.sessionID !== void 0 ? { sessionID: ctx.sessionID } : {}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var wrangler_check = tool({
|
|
47
|
+
description: "AiWrangler post-generation gate. Call BEFORE declaring a task done (and after each substantive revision). Runs deterministic checks (review rules, em dashes, deferred-work discipline, pub-API surface, test discipline) plus a Jev semantic battery (claims vs diff, scope discipline, silent defaults, comment truthfulness, spec contradictions, self-report verification) against the dirty files across all tracked repos. Returns PASS / FIX REQUIRED (with a cited, actionable fix list) / HUMAN REVIEW / DEGRADED. Fix every cited finding and re-run until PASS. Pass agent_summary and tool_output to have your status claims verified too.",
|
|
48
|
+
args: {
|
|
49
|
+
task: tool.schema.string().optional().describe("the task being implemented (improves scope and spec checks)"),
|
|
50
|
+
agent_summary: tool.schema.string().optional().describe("your summary of what you did and its status (e.g. 'tests pass, lint clean')"),
|
|
51
|
+
tool_output: tool.schema.string().optional().describe("the raw output of the build/test commands you ran, for self-report verification"),
|
|
52
|
+
cargo: tool.schema.boolean().default(false).describe("also run cargo check on dirty repos"),
|
|
53
|
+
tests: tool.schema.boolean().default(false).describe("also run cargo test on dirty repos (implies cargo)")
|
|
54
|
+
},
|
|
55
|
+
async execute(args, ctx) {
|
|
56
|
+
const dir = ctx.directory ?? process.cwd();
|
|
57
|
+
return checkGate(dir, {
|
|
58
|
+
...args.task ? { task: args.task } : {},
|
|
59
|
+
...args.agent_summary ? { agentSummary: args.agent_summary } : {},
|
|
60
|
+
...args.tool_output ? { toolOutput: args.tool_output } : {},
|
|
61
|
+
cargo: args.cargo || args.tests || false,
|
|
62
|
+
tests: args.tests || false,
|
|
63
|
+
...ctx.sessionID !== void 0 ? { sessionID: ctx.sessionID } : {}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
var wrangler_ack = tool({
|
|
68
|
+
description: "AiWrangler HUMAN REVIEW. With no rules, note, or diffHash, lists pending human-severity findings and the diff hash. Passing rules requests an acknowledgment. Those arguments do not approve it: this native tool has no MCP elicitation channel, so it shows a desktop dialog. Nothing is recorded unless the owner approves. Prefer the MCP server inside opencode, which can elicit in the editor. Block-severity findings cannot be acked.",
|
|
69
|
+
args: {
|
|
70
|
+
rules: tool.schema.array(tool.schema.string()).optional().describe("rule IDs to ask the owner to acknowledge. Does not approve by itself."),
|
|
71
|
+
note: tool.schema.string().optional().describe("shown to the owner on the approval prompt and stored if they approve"),
|
|
72
|
+
diffHash: tool.schema.string().optional().describe("diff hash from the check report; defaults to the latest recorded gate run")
|
|
73
|
+
},
|
|
74
|
+
async execute(args, ctx) {
|
|
75
|
+
const dir = ctx.directory ?? process.cwd();
|
|
76
|
+
if (!isAckWriteAttempt(args)) return listPendingAck(dir);
|
|
77
|
+
return requestHumanAck(
|
|
78
|
+
dir,
|
|
79
|
+
{ rules: args.rules ?? [], ...args.note !== void 0 ? { note: args.note } : {}, ...args.diffHash !== void 0 ? { diffHash: args.diffHash } : {} },
|
|
80
|
+
{ by: "opencode" }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
var wrangler_override = tool({
|
|
85
|
+
description: "AiWrangler runtime overrides. With no set, clear, or note, reports active overrides. Passing set or clear requests a change. Those arguments do not apply it: this native tool shows a desktop dialog because it has no MCP elicitation channel. Nothing changes unless the owner approves. Prefer the MCP server inside opencode. Overrides expire on the next PASS.",
|
|
86
|
+
args: {
|
|
87
|
+
set: tool.schema.object({
|
|
88
|
+
activePolicy: tool.schema.string().optional(),
|
|
89
|
+
egressSemantic: tool.schema.boolean().optional(),
|
|
90
|
+
actionThreshold: tool.schema.number().min(0).max(1).optional(),
|
|
91
|
+
reviewThreshold: tool.schema.number().min(0).max(1).optional(),
|
|
92
|
+
gates: tool.schema.record(tool.schema.string(), tool.schema.boolean()).optional()
|
|
93
|
+
}).optional().describe("overrides to ask the owner to apply. Does not apply them by itself."),
|
|
94
|
+
clear: tool.schema.boolean().optional().describe("ask the owner to remove active overrides. Does not clear them by itself."),
|
|
95
|
+
note: tool.schema.string().optional().describe("shown to the owner on the approval prompt and stored if they approve")
|
|
96
|
+
},
|
|
97
|
+
async execute(args, ctx) {
|
|
98
|
+
const dir = ctx.directory ?? process.cwd();
|
|
99
|
+
if (!isOverrideWriteAttempt(args)) return reportOverrides(dir);
|
|
100
|
+
return requestHumanOverride(
|
|
101
|
+
dir,
|
|
102
|
+
{
|
|
103
|
+
...args.set !== void 0 ? { set: args.set } : {},
|
|
104
|
+
...args.clear === true ? { clear: true } : {},
|
|
105
|
+
...args.note !== void 0 ? { note: args.note } : {}
|
|
106
|
+
},
|
|
107
|
+
{ by: "opencode" }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
var wrangler_loop_reset = tool({
|
|
112
|
+
description: "AiWrangler circuit-breaker reset. Call ONLY after the user has decided repeated findings are not making progress: clears the retry loop state so the next check starts fresh, and audits a notice. No-op when there is no loop state.",
|
|
113
|
+
args: {},
|
|
114
|
+
async execute(_args, ctx) {
|
|
115
|
+
const dir = ctx.directory ?? process.cwd();
|
|
116
|
+
return loopResetGate(dir, "opencode");
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
export {
|
|
120
|
+
wrangler_ack,
|
|
121
|
+
wrangler_check,
|
|
122
|
+
wrangler_config,
|
|
123
|
+
wrangler_loop_reset,
|
|
124
|
+
wrangler_override,
|
|
125
|
+
wrangler_plan
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools.ts"],"sourcesContent":["import { tool } from \"@opencode-ai/plugin\"\nimport {\n configReport,\n planGate,\n checkGate,\n isAckWriteAttempt,\n isOverrideWriteAttempt,\n listPendingAck,\n reportOverrides,\n requestHumanAck,\n requestHumanOverride,\n loopResetGate,\n} from \"./core.js\"\nimport type { RuntimeOverrides } from \"./overrides.js\"\n\nexport const wrangler_config = tool({\n description:\n \"AiWrangler ground-truth and gate status. Prints the effective configuration (including AIWRANGLER_* env overrides), every gate flag, the active policy thresholds, and the loaded corpus summary (spec sections, review rules, divergence rulings, open questions, one-way doors, whitepaper sections, corpus hash). Use this first in any session to confirm what AiWrangler is enforcing and that the corpus is present.\",\n args: {},\n async execute(_args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n try {\n return configReport(dir)\n } catch (err) {\n return `error: ${err instanceof Error ? err.message : String(err)}`\n }\n },\n})\n\nexport const wrangler_plan = tool({\n description:\n \"AiWrangler pre-flight plan gate. Call BEFORE writing any code. Judges the task against the ground-truth corpus (protocol spec, review rules, divergence rulings, open questions, one-way doors) and returns: an ACTION (pass | clarify | human), the reasons, and a ground-truth context pack you MUST honor while implementing. clarify means the task is ambiguous or touches an unresolved question: ask the user before coding. human means a one-way door or steering risk: stop for the owner.\",\n args: {\n task: tool.schema.string().describe(\"the task as given, verbatim if possible\"),\n plan: tool.schema.string().optional().describe(\"your implementation plan, if you have one drafted\"),\n },\n async execute(args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n return planGate(dir, {\n task: args.task,\n ...(args.plan ? { plan: args.plan } : {}),\n ...(ctx.sessionID !== undefined ? { sessionID: ctx.sessionID } : {}),\n })\n },\n})\n\nexport const wrangler_check = tool({\n description:\n \"AiWrangler post-generation gate. Call BEFORE declaring a task done (and after each substantive revision). Runs deterministic checks (review rules, em dashes, deferred-work discipline, pub-API surface, test discipline) plus a Jev semantic battery (claims vs diff, scope discipline, silent defaults, comment truthfulness, spec contradictions, self-report verification) against the dirty files across all tracked repos. Returns PASS / FIX REQUIRED (with a cited, actionable fix list) / HUMAN REVIEW / DEGRADED. Fix every cited finding and re-run until PASS. Pass agent_summary and tool_output to have your status claims verified too.\",\n args: {\n task: tool.schema.string().optional().describe(\"the task being implemented (improves scope and spec checks)\"),\n agent_summary: tool.schema.string().optional().describe(\"your summary of what you did and its status (e.g. 'tests pass, lint clean')\"),\n tool_output: tool.schema.string().optional().describe(\"the raw output of the build/test commands you ran, for self-report verification\"),\n cargo: tool.schema.boolean().default(false).describe(\"also run cargo check on dirty repos\"),\n tests: tool.schema.boolean().default(false).describe(\"also run cargo test on dirty repos (implies cargo)\"),\n },\n async execute(args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n return checkGate(dir, {\n ...(args.task ? { task: args.task } : {}),\n ...(args.agent_summary ? { agentSummary: args.agent_summary } : {}),\n ...(args.tool_output ? { toolOutput: args.tool_output } : {}),\n cargo: args.cargo || args.tests || false,\n tests: args.tests || false,\n ...(ctx.sessionID !== undefined ? { sessionID: ctx.sessionID } : {}),\n })\n },\n})\n\nexport const wrangler_ack = tool({\n description:\n \"AiWrangler HUMAN REVIEW. With no rules, note, or diffHash, lists pending human-severity findings and the diff hash. Passing rules requests an acknowledgment. Those arguments do not approve it: this native tool has no MCP elicitation channel, so it shows a desktop dialog. Nothing is recorded unless the owner approves. Prefer the MCP server inside opencode, which can elicit in the editor. Block-severity findings cannot be acked.\",\n args: {\n rules: tool.schema.array(tool.schema.string()).optional().describe(\"rule IDs to ask the owner to acknowledge. Does not approve by itself.\"),\n note: tool.schema.string().optional().describe(\"shown to the owner on the approval prompt and stored if they approve\"),\n diffHash: tool.schema.string().optional().describe(\"diff hash from the check report; defaults to the latest recorded gate run\"),\n },\n async execute(args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n if (!isAckWriteAttempt(args)) return listPendingAck(dir)\n return requestHumanAck(\n dir,\n { rules: args.rules ?? [], ...(args.note !== undefined ? { note: args.note } : {}), ...(args.diffHash !== undefined ? { diffHash: args.diffHash } : {}) },\n { by: \"opencode\" },\n )\n },\n})\n\nexport const wrangler_override = tool({\n description:\n \"AiWrangler runtime overrides. With no set, clear, or note, reports active overrides. Passing set or clear requests a change. Those arguments do not apply it: this native tool shows a desktop dialog because it has no MCP elicitation channel. Nothing changes unless the owner approves. Prefer the MCP server inside opencode. Overrides expire on the next PASS.\",\n args: {\n set: tool.schema\n .object({\n activePolicy: tool.schema.string().optional(),\n egressSemantic: tool.schema.boolean().optional(),\n actionThreshold: tool.schema.number().min(0).max(1).optional(),\n reviewThreshold: tool.schema.number().min(0).max(1).optional(),\n gates: tool.schema.record(tool.schema.string(), tool.schema.boolean()).optional(),\n })\n .optional()\n .describe(\"overrides to ask the owner to apply. Does not apply them by itself.\"),\n clear: tool.schema.boolean().optional().describe(\"ask the owner to remove active overrides. Does not clear them by itself.\"),\n note: tool.schema.string().optional().describe(\"shown to the owner on the approval prompt and stored if they approve\"),\n },\n async execute(args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n if (!isOverrideWriteAttempt(args)) return reportOverrides(dir)\n return requestHumanOverride(\n dir,\n {\n ...(args.set !== undefined ? { set: args.set as RuntimeOverrides } : {}),\n ...(args.clear === true ? { clear: true } : {}),\n ...(args.note !== undefined ? { note: args.note } : {}),\n },\n { by: \"opencode\" },\n )\n },\n})\n\nexport const wrangler_loop_reset = tool({\n description:\n \"AiWrangler circuit-breaker reset. Call ONLY after the user has decided repeated findings are not making progress: clears the retry loop state so the next check starts fresh, and audits a notice. No-op when there is no loop state.\",\n args: {},\n async execute(_args, ctx) {\n const dir = ctx.directory ?? process.cwd()\n return loopResetGate(dir, \"opencode\")\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAed,IAAM,kBAAkB,KAAK;AAAA,EAClC,aACE;AAAA,EACF,MAAM,CAAC;AAAA,EACP,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,QAAI;AACF,aAAO,aAAa,GAAG;AAAA,IACzB,SAAS,KAAK;AACZ,aAAO,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IACnE;AAAA,EACF;AACF,CAAC;AAEM,IAAM,gBAAgB,KAAK;AAAA,EAChC,aACE;AAAA,EACF,MAAM;AAAA,IACJ,MAAM,KAAK,OAAO,OAAO,EAAE,SAAS,yCAAyC;AAAA,IAC7E,MAAM,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,mDAAmD;AAAA,EACpG;AAAA,EACA,MAAM,QAAQ,MAAM,KAAK;AACvB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,WAAO,SAAS,KAAK;AAAA,MACnB,MAAM,KAAK;AAAA,MACX,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACvC,GAAI,IAAI,cAAc,SAAY,EAAE,WAAW,IAAI,UAAU,IAAI,CAAC;AAAA,IACpE,CAAC;AAAA,EACH;AACF,CAAC;AAEM,IAAM,iBAAiB,KAAK;AAAA,EACjC,aACE;AAAA,EACF,MAAM;AAAA,IACJ,MAAM,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,6DAA6D;AAAA,IAC5G,eAAe,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,6EAA6E;AAAA,IACrI,aAAa,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,iFAAiF;AAAA,IACvI,OAAO,KAAK,OAAO,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,qCAAqC;AAAA,IAC1F,OAAO,KAAK,OAAO,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,oDAAoD;AAAA,EAC3G;AAAA,EACA,MAAM,QAAQ,MAAM,KAAK;AACvB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,WAAO,UAAU,KAAK;AAAA,MACpB,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACvC,GAAI,KAAK,gBAAgB,EAAE,cAAc,KAAK,cAAc,IAAI,CAAC;AAAA,MACjE,GAAI,KAAK,cAAc,EAAE,YAAY,KAAK,YAAY,IAAI,CAAC;AAAA,MAC3D,OAAO,KAAK,SAAS,KAAK,SAAS;AAAA,MACnC,OAAO,KAAK,SAAS;AAAA,MACrB,GAAI,IAAI,cAAc,SAAY,EAAE,WAAW,IAAI,UAAU,IAAI,CAAC;AAAA,IACpE,CAAC;AAAA,EACH;AACF,CAAC;AAEM,IAAM,eAAe,KAAK;AAAA,EAC/B,aACE;AAAA,EACF,MAAM;AAAA,IACJ,OAAO,KAAK,OAAO,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,uEAAuE;AAAA,IAC1I,MAAM,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,sEAAsE;AAAA,IACrH,UAAU,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,2EAA2E;AAAA,EAChI;AAAA,EACA,MAAM,QAAQ,MAAM,KAAK;AACvB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,QAAI,CAAC,kBAAkB,IAAI,EAAG,QAAO,eAAe,GAAG;AACvD,WAAO;AAAA,MACL;AAAA,MACA,EAAE,OAAO,KAAK,SAAS,CAAC,GAAG,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC,GAAI,GAAI,KAAK,aAAa,SAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC,EAAG;AAAA,MACxJ,EAAE,IAAI,WAAW;AAAA,IACnB;AAAA,EACF;AACF,CAAC;AAEM,IAAM,oBAAoB,KAAK;AAAA,EACpC,aACE;AAAA,EACF,MAAM;AAAA,IACJ,KAAK,KAAK,OACP,OAAO;AAAA,MACN,cAAc,KAAK,OAAO,OAAO,EAAE,SAAS;AAAA,MAC5C,gBAAgB,KAAK,OAAO,QAAQ,EAAE,SAAS;AAAA,MAC/C,iBAAiB,KAAK,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAC7D,iBAAiB,KAAK,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAC7D,OAAO,KAAK,OAAO,OAAO,KAAK,OAAO,OAAO,GAAG,KAAK,OAAO,QAAQ,CAAC,EAAE,SAAS;AAAA,IAClF,CAAC,EACA,SAAS,EACT,SAAS,qEAAqE;AAAA,IACjF,OAAO,KAAK,OAAO,QAAQ,EAAE,SAAS,EAAE,SAAS,0EAA0E;AAAA,IAC3H,MAAM,KAAK,OAAO,OAAO,EAAE,SAAS,EAAE,SAAS,sEAAsE;AAAA,EACvH;AAAA,EACA,MAAM,QAAQ,MAAM,KAAK;AACvB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,QAAI,CAAC,uBAAuB,IAAI,EAAG,QAAO,gBAAgB,GAAG;AAC7D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAwB,IAAI,CAAC;AAAA,QACtE,GAAI,KAAK,UAAU,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;AAAA,QAC7C,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,MACvD;AAAA,MACA,EAAE,IAAI,WAAW;AAAA,IACnB;AAAA,EACF;AACF,CAAC;AAEM,IAAM,sBAAsB,KAAK;AAAA,EACtC,aACE;AAAA,EACF,MAAM,CAAC;AAAA,EACP,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,MAAM,IAAI,aAAa,QAAQ,IAAI;AACzC,WAAO,cAAc,KAAK,UAAU;AAAA,EACtC;AACF,CAAC;","names":[]}
|