@agentbridge1/cli 0.0.7 → 0.0.9
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/dist/build-info.json +4 -4
- package/dist/commands/connect.js +58 -122
- package/dist/commands/doctor.js +46 -8
- package/dist/commands/setup-mcp.js +54 -44
- package/dist/commands/start.js +86 -22
- package/dist/commands/watch.js +662 -92
- package/dist/contract-intelligence.js +597 -0
- package/dist/contract-verdict.js +239 -0
- package/dist/diff-reader.js +200 -0
- package/dist/error-catalog.js +29 -0
- package/dist/git-status.js +6 -2
- package/dist/index.js +11 -5
- package/dist/intent-parser.js +178 -0
- package/dist/intent-validation.js +37 -0
- package/dist/local-proof.js +12 -4
- package/dist/mcp/agentbridge-mcp.js +1174 -37
- package/dist/mcp/agentbridge-mcp.js.map +4 -4
- package/dist/mcp-config.js +64 -0
- package/dist/proof-parser.js +118 -0
- package/dist/session-state.js +15 -0
- package/dist/session.js +10 -0
- package/dist/supervision.js +191 -48
- package/dist/test-runner.js +201 -15
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Intent parser — extracts structured task specification from a raw intent string.
|
|
4
|
+
* All logic is deterministic: no LLM, no external calls.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseIntent = parseIntent;
|
|
8
|
+
exports.buildTaskSpecificCriteria = buildTaskSpecificCriteria;
|
|
9
|
+
const ACTIONS = ["fix", "add", "update", "refactor", "remove", "migrate", "rename", "revert", "delete", "implement", "improve", "patch"];
|
|
10
|
+
const DOMAIN_KEYWORD_MAP = {
|
|
11
|
+
auth: ["auth", "login", "logout", "session", "token", "oauth", "jwt", "password", "signin", "signup", "sign-in", "sign-up", "register", "credential", "permission", "role"],
|
|
12
|
+
database: ["database", "db", "schema", "migration", "migrate", "prisma", "sql", "query", "table", "column", "index", "model", "seed", "orm"],
|
|
13
|
+
payments: ["payment", "payments", "billing", "stripe", "invoice", "subscription", "checkout", "webhook"],
|
|
14
|
+
api: ["api", "endpoint", "route", "handler", "controller", "request", "response", "rest", "graphql"],
|
|
15
|
+
ui: ["ui", "style", "css", "component", "page", "layout", "copy", "design", "render", "display", "view", "frontend", "html", "template"],
|
|
16
|
+
mcp: ["mcp", "agentbridge", "cursor", "rules"],
|
|
17
|
+
tests: ["test", "tests", "spec", "vitest", "jest", "coverage", "proof"],
|
|
18
|
+
};
|
|
19
|
+
const SYMPTOMS = {
|
|
20
|
+
"401": ["401", "unauthorized", "unauthenticated"],
|
|
21
|
+
"403": ["403", "forbidden"],
|
|
22
|
+
"404": ["404", "not found", "notfound"],
|
|
23
|
+
"500": ["500", "internal server error", "server error"],
|
|
24
|
+
"null": ["null", "undefined", "nan", "nil"],
|
|
25
|
+
"crash": ["crash", "crashes", "crashed", "exception", "throws", "throw"],
|
|
26
|
+
"loop": ["loop", "infinite loop", "recursion"],
|
|
27
|
+
"hang": ["hang", "hangs", "timeout", "deadlock"],
|
|
28
|
+
"slow": ["slow", "performance", "latency", "memory leak", "memory"],
|
|
29
|
+
"duplicate": ["duplicate", "duplicated", "twice"],
|
|
30
|
+
"missing": ["missing", "not showing", "not found"],
|
|
31
|
+
};
|
|
32
|
+
// Route pattern: /something or /something/else (no file extension)
|
|
33
|
+
const ROUTE_RE = /(?<!\w)(\/[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_:_-]+)*)/g;
|
|
34
|
+
// File with extension: something.ts, SomeThing.tsx, etc.
|
|
35
|
+
const FILE_RE = /\b([A-Za-z][A-Za-z0-9_-]*\.[a-z]{2,4})\b/g;
|
|
36
|
+
// PascalCase class or model name (≥2 caps or starts with uppercase followed by lowercase+uppercase)
|
|
37
|
+
const PASCAL_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]*)+)\b/g;
|
|
38
|
+
// camelCase function name — at least one internal uppercase letter, no spaces
|
|
39
|
+
const CAMEL_FN_RE = /\b([a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)+)\b/g;
|
|
40
|
+
// Quoted phrase
|
|
41
|
+
const QUOTED_RE = /"([^"]+)"|'([^']+)'/g;
|
|
42
|
+
function detectDomain(lower) {
|
|
43
|
+
for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORD_MAP)) {
|
|
44
|
+
if (keywords.some((kw) => lower.includes(kw)))
|
|
45
|
+
return domain;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function detectAction(lower) {
|
|
50
|
+
for (const action of ACTIONS) {
|
|
51
|
+
if (lower.startsWith(action) || lower.includes(` ${action} `))
|
|
52
|
+
return action;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function detectSymptom(lower) {
|
|
57
|
+
for (const [symptom, patterns] of Object.entries(SYMPTOMS)) {
|
|
58
|
+
if (patterns.some((p) => lower.includes(p)))
|
|
59
|
+
return symptom;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function extractTargets(raw) {
|
|
64
|
+
const targets = new Set();
|
|
65
|
+
// Quoted phrases first
|
|
66
|
+
for (const m of raw.matchAll(QUOTED_RE)) {
|
|
67
|
+
const phrase = m[1] ?? m[2];
|
|
68
|
+
if (phrase)
|
|
69
|
+
targets.add(phrase.trim());
|
|
70
|
+
}
|
|
71
|
+
// API routes
|
|
72
|
+
for (const m of raw.matchAll(ROUTE_RE)) {
|
|
73
|
+
targets.add(m[1]);
|
|
74
|
+
}
|
|
75
|
+
// Files with extensions
|
|
76
|
+
for (const m of raw.matchAll(FILE_RE)) {
|
|
77
|
+
targets.add(m[1]);
|
|
78
|
+
}
|
|
79
|
+
// PascalCase (models, classes)
|
|
80
|
+
for (const m of raw.matchAll(PASCAL_RE)) {
|
|
81
|
+
targets.add(m[1]);
|
|
82
|
+
}
|
|
83
|
+
// camelCase function names — only when short enough to be a name, not prose
|
|
84
|
+
for (const m of raw.matchAll(CAMEL_FN_RE)) {
|
|
85
|
+
if (m[1].length <= 40)
|
|
86
|
+
targets.add(m[1]);
|
|
87
|
+
}
|
|
88
|
+
return [...targets];
|
|
89
|
+
}
|
|
90
|
+
function inferLayers(domain, targets, symptom, action, rawLower) {
|
|
91
|
+
const layers = [];
|
|
92
|
+
const hasRoute = targets.some((t) => t.startsWith("/"));
|
|
93
|
+
const hasMigration = action === "migrate" || targets.some((t) => t.toLowerCase().includes("migration"));
|
|
94
|
+
if (domain === "auth") {
|
|
95
|
+
if (symptom === "401" || symptom === "403")
|
|
96
|
+
layers.push("route_handler", "auth_middleware");
|
|
97
|
+
else if (hasRoute)
|
|
98
|
+
layers.push("route_handler", "auth_middleware");
|
|
99
|
+
else
|
|
100
|
+
layers.push("auth_middleware");
|
|
101
|
+
}
|
|
102
|
+
if (domain === "api") {
|
|
103
|
+
if (hasRoute)
|
|
104
|
+
layers.push("route_handler");
|
|
105
|
+
else
|
|
106
|
+
layers.push("route_handler");
|
|
107
|
+
}
|
|
108
|
+
if (domain === "database") {
|
|
109
|
+
if (hasMigration)
|
|
110
|
+
layers.push("schema", "migration_file");
|
|
111
|
+
else
|
|
112
|
+
layers.push("model", "query");
|
|
113
|
+
}
|
|
114
|
+
if (domain === "ui") {
|
|
115
|
+
layers.push("component", "view");
|
|
116
|
+
}
|
|
117
|
+
if (domain === "payments") {
|
|
118
|
+
if (targets.some((t) => t.toLowerCase().includes("webhook")) || rawLower.includes("webhook")) {
|
|
119
|
+
layers.push("webhook_handler");
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
layers.push("billing_handler");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (domain === "mcp") {
|
|
126
|
+
layers.push("mcp_server", "config");
|
|
127
|
+
}
|
|
128
|
+
return layers;
|
|
129
|
+
}
|
|
130
|
+
function parseIntent(raw) {
|
|
131
|
+
if (!raw?.trim()) {
|
|
132
|
+
return { raw: raw ?? "", domain: null, targets: [], symptom: null, action: null, affected_layer: [] };
|
|
133
|
+
}
|
|
134
|
+
const lower = raw.toLowerCase();
|
|
135
|
+
const domain = detectDomain(lower);
|
|
136
|
+
const action = detectAction(lower);
|
|
137
|
+
const symptom = detectSymptom(lower);
|
|
138
|
+
const targets = extractTargets(raw);
|
|
139
|
+
const affected_layer = inferLayers(domain, targets, symptom, action, lower);
|
|
140
|
+
return { raw, domain, targets, symptom, action, affected_layer };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Builds task-specific criteria to prepend/append to a profile's generic criteria.
|
|
144
|
+
* Returns at most 2 sentences so the checklist stays readable.
|
|
145
|
+
*/
|
|
146
|
+
function buildTaskSpecificCriteria(parsed) {
|
|
147
|
+
const extra = [];
|
|
148
|
+
const routeTargets = parsed.targets.filter((t) => t.startsWith("/"));
|
|
149
|
+
const fileTargets = parsed.targets.filter((t) => t.includes("."));
|
|
150
|
+
const namedTargets = parsed.targets.filter((t) => !t.startsWith("/") && !t.includes("."));
|
|
151
|
+
// Task-specific entry criterion (what to look at)
|
|
152
|
+
if (routeTargets.length > 0 && parsed.domain === "auth" && parsed.symptom) {
|
|
153
|
+
extra.push(`The ${routeTargets[0]} route handler or its auth middleware chain is updated to address the ${parsed.symptom}.`);
|
|
154
|
+
}
|
|
155
|
+
else if (routeTargets.length > 0 && parsed.domain === "api") {
|
|
156
|
+
extra.push(`The ${routeTargets[0]} endpoint handler is updated as intended.`);
|
|
157
|
+
}
|
|
158
|
+
else if (routeTargets.length > 0) {
|
|
159
|
+
extra.push(`Changes are focused on the ${routeTargets[0]} route.`);
|
|
160
|
+
}
|
|
161
|
+
else if (fileTargets.length > 0) {
|
|
162
|
+
extra.push(`Changes are focused on ${fileTargets[0]}.`);
|
|
163
|
+
}
|
|
164
|
+
else if (namedTargets.length > 0) {
|
|
165
|
+
extra.push(`Changes are focused on ${namedTargets[0]}.`);
|
|
166
|
+
}
|
|
167
|
+
// Task-specific proof criterion (what to verify)
|
|
168
|
+
if (parsed.symptom && routeTargets.length > 0) {
|
|
169
|
+
extra.push(`Proof exists for the ${parsed.symptom} behavior on ${routeTargets[0]}.`);
|
|
170
|
+
}
|
|
171
|
+
else if (parsed.symptom) {
|
|
172
|
+
extra.push(`Proof exists that the ${parsed.symptom} condition is resolved.`);
|
|
173
|
+
}
|
|
174
|
+
else if (parsed.action && parsed.domain) {
|
|
175
|
+
extra.push(`Proof exists that the ${parsed.action} to the ${parsed.domain} layer is correct.`);
|
|
176
|
+
}
|
|
177
|
+
return extra;
|
|
178
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isVagueIntent = isVagueIntent;
|
|
4
|
+
exports.validateIntent = validateIntent;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const VAGUE_PHRASES = [
|
|
7
|
+
"working on project",
|
|
8
|
+
"making changes",
|
|
9
|
+
"updating files",
|
|
10
|
+
"fix stuff",
|
|
11
|
+
"general work",
|
|
12
|
+
];
|
|
13
|
+
function isVagueIntent(intent) {
|
|
14
|
+
const lower = intent.trim().toLowerCase();
|
|
15
|
+
if (!lower)
|
|
16
|
+
return true;
|
|
17
|
+
if (lower.split(/\s+/).length < 4)
|
|
18
|
+
return true;
|
|
19
|
+
return VAGUE_PHRASES.some((phrase) => lower.includes(phrase));
|
|
20
|
+
}
|
|
21
|
+
function validateIntent(intent) {
|
|
22
|
+
const trimmed = intent.trim();
|
|
23
|
+
if (!trimmed) {
|
|
24
|
+
throw (0, errors_1.catalogCliError)("START_SUMMARY_REQUIRED", {
|
|
25
|
+
what: "A work contract intent is required.",
|
|
26
|
+
next: 'Run: agentbridge start "Fix auth 401 on /rooms endpoint"',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (isVagueIntent(trimmed)) {
|
|
30
|
+
throw (0, errors_1.catalogCliError)("START_SUMMARY_REQUIRED", {
|
|
31
|
+
what: "Intent is too vague for a work contract.",
|
|
32
|
+
why: "AgentBridge needs a specific goal to compare against file changes.",
|
|
33
|
+
next: 'Use a concrete intent, e.g. agentbridge start "Fix auth 401 on /rooms endpoint"',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
package/dist/local-proof.js
CHANGED
|
@@ -11,10 +11,18 @@ function normalizePath(path) {
|
|
|
11
11
|
return path.trim().replaceAll("\\", "/");
|
|
12
12
|
}
|
|
13
13
|
function isProofNoiseFile(file) {
|
|
14
|
-
const normalized = normalizePath(file)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const normalized = normalizePath(file);
|
|
15
|
+
const lower = normalized.toLowerCase();
|
|
16
|
+
if (lower === "agentbridge.md" ||
|
|
17
|
+
lower === ".cursor" ||
|
|
18
|
+
lower.startsWith(".cursor/")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
// Vite ephemeral config bundles (e.g. vite.config.ts.timestamp-1781442511868-*.mjs)
|
|
22
|
+
if (/\.timestamp-\d+-[a-z0-9]+\.mjs$/i.test(normalized)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
18
26
|
}
|
|
19
27
|
function normalizeProofMatchingFileSet(files) {
|
|
20
28
|
return [...new Set(files.map(normalizePath).filter((file) => file.length > 0 && !isProofNoiseFile(file)))].sort();
|