@agentbridge1/cli 0.0.5 → 0.0.6

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.
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isActiveLocalSession = isActiveLocalSession;
4
+ exports.resolveRepoRoot = resolveRepoRoot;
3
5
  exports.readSessionState = readSessionState;
4
6
  exports.writeSessionState = writeSessionState;
5
7
  exports.archiveClosedSession = archiveClosedSession;
@@ -8,11 +10,34 @@ exports.readRecentHandoffs = readRecentHandoffs;
8
10
  exports.clearSessionState = clearSessionState;
9
11
  const node_fs_1 = require("node:fs");
10
12
  const node_path_1 = require("node:path");
11
- const SESSION_PATH = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "session.json");
12
- const SESSIONS_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "sessions");
13
- const HANDOFFS_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "handoffs");
14
- const SESSION_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge");
15
- const SESSION_BACKUP_PATH = (0, node_path_1.resolve)(SESSION_DIR, "session.json.bak");
13
+ const local_proof_1 = require("./local-proof");
14
+ function isActiveLocalSession(state) {
15
+ if (!state)
16
+ return false;
17
+ if (state.status === "closed")
18
+ return false;
19
+ if (!state.id || state.id === "none")
20
+ return false;
21
+ return true;
22
+ }
23
+ function resolveRepoRoot() {
24
+ return process.env.AGENTBRIDGE_REPO_ROOT?.trim() || process.cwd();
25
+ }
26
+ function sessionDir() {
27
+ return (0, node_path_1.resolve)(resolveRepoRoot(), ".agentbridge");
28
+ }
29
+ function sessionPath() {
30
+ return (0, node_path_1.resolve)(sessionDir(), "session.json");
31
+ }
32
+ function sessionsDir() {
33
+ return (0, node_path_1.resolve)(sessionDir(), "sessions");
34
+ }
35
+ function handoffsDir() {
36
+ return (0, node_path_1.resolve)(sessionDir(), "handoffs");
37
+ }
38
+ function sessionBackupPath() {
39
+ return (0, node_path_1.resolve)(sessionDir(), "session.json.bak");
40
+ }
16
41
  function isRecord(value) {
17
42
  return typeof value === "object" && value !== null;
18
43
  }
@@ -28,6 +53,49 @@ function isBoundaryCrossingArray(value) {
28
53
  function isScopedApprovalArray(value) {
29
54
  return Array.isArray(value);
30
55
  }
56
+ const AGENT_ACTIVITY_EVENTS = new Set([
57
+ "start_work_session",
58
+ "claim_lane",
59
+ "release_lane",
60
+ "update_work_session_scope",
61
+ "post_handoff",
62
+ "close_work_session",
63
+ ]);
64
+ function isAgentActivity(value) {
65
+ if (!isRecord(value))
66
+ return false;
67
+ if (value.source !== "mcp")
68
+ return false;
69
+ if (typeof value.updatedAt !== "string" || value.updatedAt.length === 0)
70
+ return false;
71
+ if (!AGENT_ACTIVITY_EVENTS.has(value.lastEvent))
72
+ return false;
73
+ if (value.intent !== undefined && typeof value.intent !== "string")
74
+ return false;
75
+ if (value.claimedPaths !== undefined && !isStringArray(value.claimedPaths))
76
+ return false;
77
+ if (value.workSessionId !== undefined && typeof value.workSessionId !== "string")
78
+ return false;
79
+ if (value.changeRequestId !== undefined && typeof value.changeRequestId !== "string")
80
+ return false;
81
+ if (value.laneDomain !== undefined && value.laneDomain !== null && typeof value.laneDomain !== "string") {
82
+ return false;
83
+ }
84
+ if (value.laneId !== undefined && typeof value.laneId !== "string")
85
+ return false;
86
+ if (value.laneType !== undefined && !["product_area", "file_path", "task", "system"].includes(String(value.laneType))) {
87
+ return false;
88
+ }
89
+ if (value.laneValue !== undefined && typeof value.laneValue !== "string")
90
+ return false;
91
+ if (value.summary !== undefined && typeof value.summary !== "string")
92
+ return false;
93
+ if (value.closedAt !== undefined && typeof value.closedAt !== "string")
94
+ return false;
95
+ if (value.handoffId !== undefined && typeof value.handoffId !== "string")
96
+ return false;
97
+ return true;
98
+ }
31
99
  function isLocalSessionState(value) {
32
100
  if (!isRecord(value))
33
101
  return false;
@@ -49,30 +117,57 @@ function isLocalSessionState(value) {
49
117
  return false;
50
118
  if (typeof value.createdAt !== "string" || value.createdAt.length === 0)
51
119
  return false;
120
+ if (value.intent !== undefined && typeof value.intent !== "string")
121
+ return false;
122
+ if (value.mode !== undefined &&
123
+ value.mode !== "local_supervision" &&
124
+ value.mode !== "strict_tracked") {
125
+ return false;
126
+ }
127
+ if (value.updatedAt !== undefined && typeof value.updatedAt !== "string")
128
+ return false;
129
+ if (value.closedAt !== undefined && typeof value.closedAt !== "string")
130
+ return false;
52
131
  if (value.claimedPaths !== undefined && !isStringArray(value.claimedPaths))
53
132
  return false;
133
+ if (value.lastLocalVerificationRun !== undefined &&
134
+ !(0, local_proof_1.isLocalVerificationRun)(value.lastLocalVerificationRun)) {
135
+ return false;
136
+ }
137
+ if (value.agentActivity !== undefined && !isAgentActivity(value.agentActivity)) {
138
+ return false;
139
+ }
54
140
  return true;
55
141
  }
142
+ function sanitizeAgentActivity(state) {
143
+ if (state.agentActivity !== undefined && !isAgentActivity(state.agentActivity)) {
144
+ const { agentActivity: _removed, ...rest } = state;
145
+ return rest;
146
+ }
147
+ return state;
148
+ }
56
149
  function readJsonFile(path) {
57
150
  return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
58
151
  }
59
152
  function quarantineInvalidSessionFile() {
60
- if (!(0, node_fs_1.existsSync)(SESSION_PATH))
153
+ const path = sessionPath();
154
+ if (!(0, node_fs_1.existsSync)(path))
61
155
  return;
62
- const quarantinedPath = (0, node_path_1.resolve)(SESSION_DIR, `session.invalid-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
63
- (0, node_fs_1.renameSync)(SESSION_PATH, quarantinedPath);
156
+ const quarantinedPath = (0, node_path_1.resolve)(sessionDir(), `session.invalid-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
157
+ (0, node_fs_1.renameSync)(path, quarantinedPath);
64
158
  }
65
159
  function readSessionState() {
66
- if (!(0, node_fs_1.existsSync)(SESSION_PATH)) {
160
+ const path = sessionPath();
161
+ if (!(0, node_fs_1.existsSync)(path)) {
67
162
  return null;
68
163
  }
69
164
  try {
70
- const parsed = readJsonFile(SESSION_PATH);
165
+ const parsed = readJsonFile(path);
71
166
  if (!isLocalSessionState(parsed)) {
72
167
  quarantineInvalidSessionFile();
73
168
  return null;
74
169
  }
75
- return parsed;
170
+ return sanitizeAgentActivity(parsed);
76
171
  }
77
172
  catch {
78
173
  quarantineInvalidSessionFile();
@@ -83,8 +178,10 @@ function writeSessionState(state) {
83
178
  if (!isLocalSessionState(state)) {
84
179
  throw new Error("Refusing to write invalid session state.");
85
180
  }
86
- (0, node_fs_1.mkdirSync)(SESSION_DIR, { recursive: true });
87
- const tempPath = (0, node_path_1.resolve)(SESSION_DIR, `session.json.tmp-${process.pid.toString()}-${Date.now().toString(36)}`);
181
+ const dir = sessionDir();
182
+ const path = sessionPath();
183
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
184
+ const tempPath = (0, node_path_1.resolve)(dir, `session.json.tmp-${process.pid.toString()}-${Date.now().toString(36)}`);
88
185
  const serialized = `${JSON.stringify(state, null, 2)}\n`;
89
186
  (0, node_fs_1.writeFileSync)(tempPath, serialized, "utf8");
90
187
  const tempParsed = readJsonFile(tempPath);
@@ -92,10 +189,10 @@ function writeSessionState(state) {
92
189
  (0, node_fs_1.rmSync)(tempPath, { force: true });
93
190
  throw new Error("Refusing to persist invalid session state payload.");
94
191
  }
95
- if ((0, node_fs_1.existsSync)(SESSION_PATH)) {
96
- (0, node_fs_1.renameSync)(SESSION_PATH, SESSION_BACKUP_PATH);
192
+ if ((0, node_fs_1.existsSync)(path)) {
193
+ (0, node_fs_1.renameSync)(path, sessionBackupPath());
97
194
  }
98
- (0, node_fs_1.renameSync)(tempPath, SESSION_PATH);
195
+ (0, node_fs_1.renameSync)(tempPath, path);
99
196
  }
100
197
  function timestampPrefix(iso) {
101
198
  return iso.replace(/[:.]/g, "-");
@@ -107,20 +204,21 @@ function writeArchive(dir, state) {
107
204
  (0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(dir, fileName), `${JSON.stringify(state, null, 2)}\n`, "utf8");
108
205
  }
109
206
  function archiveClosedSession(state) {
110
- writeArchive(SESSIONS_DIR, state);
207
+ writeArchive(sessionsDir(), state);
111
208
  }
112
209
  function archiveHandoff(state) {
113
- writeArchive(HANDOFFS_DIR, state);
210
+ writeArchive(handoffsDir(), state);
114
211
  }
115
212
  function readRecentHandoffs(limit = 1) {
116
- if (!(0, node_fs_1.existsSync)(HANDOFFS_DIR)) {
213
+ const dir = handoffsDir();
214
+ if (!(0, node_fs_1.existsSync)(dir)) {
117
215
  return [];
118
216
  }
119
- const files = (0, node_fs_1.readdirSync)(HANDOFFS_DIR)
217
+ const files = (0, node_fs_1.readdirSync)(dir)
120
218
  .filter((file) => file.endsWith(".json"))
121
219
  .sort((a, b) => b.localeCompare(a))
122
220
  .slice(0, Math.max(0, limit));
123
- return files.map((file) => JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(HANDOFFS_DIR, file), "utf8")));
221
+ return files.map((file) => JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(dir, file), "utf8")));
124
222
  }
125
223
  function clearSessionState() {
126
224
  writeSessionState({
package/dist/session.js CHANGED
@@ -53,10 +53,13 @@ function captureLocalSessionBaseline() {
53
53
  function openLocalSession(input) {
54
54
  const claimedPaths = [...new Set((input.claimedPaths ?? []).map((path) => path.trim()).filter(Boolean))];
55
55
  const baseline = captureLocalSessionBaseline();
56
+ const now = baseline.startedAt;
56
57
  const state = {
57
58
  id: `local_${Date.now().toString(36)}`,
58
- agentId: input.agentId,
59
+ agentId: input.agentId?.trim() || "local",
59
60
  laneDomain: input.laneDomain,
61
+ intent: input.intent?.trim() || undefined,
62
+ mode: input.mode,
60
63
  changeRequestId: input.changeRequestId,
61
64
  claimedPaths,
62
65
  status: "active",
@@ -67,7 +70,8 @@ function openLocalSession(input) {
67
70
  serverSessionId: input.serverSessionId,
68
71
  pendingHandoffToAgent: input.pendingHandoffToAgent,
69
72
  pendingHandoffDomain: input.pendingHandoffDomain,
70
- createdAt: baseline.startedAt,
73
+ createdAt: now,
74
+ updatedAt: now,
71
75
  startedAt: baseline.startedAt,
72
76
  gitHead: baseline.gitHead,
73
77
  dirtyFilesAtStart: baseline.dirtyFilesAtStart,
@@ -81,8 +85,11 @@ function closeLocalSession(state) {
81
85
  if (!closable.ok) {
82
86
  return { ok: false, reason: closable.reason ?? "Session blocked" };
83
87
  }
88
+ const closedAt = new Date().toISOString();
84
89
  state.status = "closed";
85
90
  state.closeReason = state.closeReason ?? "completed";
91
+ state.closedAt = closedAt;
92
+ state.updatedAt = closedAt;
86
93
  (0, session_state_1.archiveClosedSession)(state);
87
94
  (0, session_state_1.writeSessionState)(state);
88
95
  return { ok: true };
@@ -5,9 +5,12 @@ exports.requiredProofHints = requiredProofHints;
5
5
  exports.computeScopeDriftAlerts = computeScopeDriftAlerts;
6
6
  exports.supervisionFromAcceptance = supervisionFromAcceptance;
7
7
  exports.fallbackSupervisionSnapshot = fallbackSupervisionSnapshot;
8
+ exports.enrichSupervisionWithLocalState = enrichSupervisionWithLocalState;
8
9
  exports.supervisionSignature = supervisionSignature;
9
10
  exports.renderSupervisionSummary = renderSupervisionSummary;
11
+ const claimed_paths_1 = require("./claimed-paths");
10
12
  const domain_resolution_1 = require("./domain-resolution");
13
+ const preflight_changed_files_1 = require("./preflight-changed-files");
11
14
  const memory_context_render_1 = require("./memory-context-render");
12
15
  const CLI_GIT_REQUIRED_PROOF = [
13
16
  "tracked modified file handled",
@@ -41,7 +44,7 @@ function requiredProofHints(workType) {
41
44
  return ["explicit verification evidence for changed docs/files"];
42
45
  return [
43
46
  "explicit verification evidence for changed files",
44
- "run agentbridge check for full acceptance state",
47
+ "run agentbridge verify, then rerun agentbridge watch",
45
48
  ];
46
49
  }
47
50
  function escapeRegex(text) {
@@ -121,7 +124,7 @@ function supervisionFromAcceptance(report, domains) {
121
124
  requiredProof,
122
125
  knownTraps: report.known_traps.slice(0, 3),
123
126
  memoryContext: report.memory_context,
124
- nextAction: "Run verification command(s), then run agentbridge check.",
127
+ nextAction: "Run `agentbridge verify -- <command>`, then rerun `agentbridge watch`.",
125
128
  driftAlerts,
126
129
  };
127
130
  }
@@ -144,11 +147,69 @@ function fallbackSupervisionSnapshot(input) {
144
147
  decision: input.unresolvedProtectedCrossing || input.blocked ? "failed" : "needs_proof",
145
148
  requiredProof,
146
149
  knownTraps: [],
147
- nextAction: "Run verification command(s), then run agentbridge check.",
150
+ nextAction: "Run `agentbridge verify -- <command>`, then rerun `agentbridge watch`.",
148
151
  driftAlerts,
149
152
  serverAcceptanceUnavailable: input.serverAcceptanceUnavailable,
150
153
  };
151
154
  }
155
+ function formatMcpEventTime(iso) {
156
+ if (!iso)
157
+ return "";
158
+ try {
159
+ const d = new Date(iso);
160
+ const h = d.getHours().toString().padStart(2, "0");
161
+ const m = d.getMinutes().toString().padStart(2, "0");
162
+ return `${h}:${m}`;
163
+ }
164
+ catch {
165
+ return "";
166
+ }
167
+ }
168
+ function effectiveDeclaredPaths(state) {
169
+ return uniqueSorted([
170
+ ...(state.claimedPaths ?? []),
171
+ ...(state.agentActivity?.claimedPaths ?? []),
172
+ ]);
173
+ }
174
+ /** Merge local session MCP mirror + post-baseline disk lens into a supervision snapshot. */
175
+ function enrichSupervisionWithLocalState(state, supervision) {
176
+ const postBaselineFiles = state.startedAt != null ? (0, preflight_changed_files_1.computeCurrentWorkFiles)(state) : [];
177
+ const totalDirty = (0, preflight_changed_files_1.repoDirtySnapshot)().length;
178
+ const claimedPaths = effectiveDeclaredPaths(state);
179
+ const driftFiles = claimedPaths.length > 0 ? (0, claimed_paths_1.filesOutsideClaimedPaths)(postBaselineFiles, claimedPaths) : [];
180
+ const declaredVsObservedDrift = claimedPaths.length > 0 && driftFiles.length > 0 ? "warn" : "none";
181
+ const activity = state.agentActivity;
182
+ const hasDeclared = activity !== undefined || Boolean(state.intent?.trim()) || claimedPaths.length > 0;
183
+ const agentDeclared = hasDeclared
184
+ ? {
185
+ intent: activity?.intent ?? state.intent,
186
+ claimedPaths,
187
+ lastEvent: activity?.lastEvent,
188
+ workSessionId: activity?.workSessionId ?? state.serverSessionId,
189
+ updatedAt: activity?.updatedAt,
190
+ summary: activity?.summary,
191
+ fromMcp: activity?.source === "mcp",
192
+ }
193
+ : undefined;
194
+ const changedFiles = postBaselineFiles.length > 0 ? postBaselineFiles : supervision.changedFiles;
195
+ return {
196
+ ...supervision,
197
+ changedFiles,
198
+ workType: inferSupervisionWorkType(changedFiles),
199
+ agentDeclared,
200
+ diskObserved: {
201
+ postBaselineFiles,
202
+ totalDirty,
203
+ },
204
+ declaredVsObservedDrift,
205
+ declaredDriftFiles: driftFiles,
206
+ scopeStatus: declaredVsObservedDrift === "warn"
207
+ ? "drift"
208
+ : supervision.scopeStatus === "unknown" && changedFiles.length > 0
209
+ ? "clean"
210
+ : supervision.scopeStatus,
211
+ };
212
+ }
152
213
  function supervisionSignature(snapshot) {
153
214
  return JSON.stringify({
154
215
  workSessionId: snapshot.workSessionId,
@@ -160,12 +221,45 @@ function supervisionSignature(snapshot) {
160
221
  requiredProof: snapshot.requiredProof,
161
222
  drift: snapshot.driftAlerts.map((a) => `${a.file}:${a.severity}:${a.likelyDomain}`),
162
223
  serverAcceptanceUnavailable: snapshot.serverAcceptanceUnavailable ? "yes" : "no",
224
+ agentDeclared: snapshot.agentDeclared
225
+ ? `${snapshot.agentDeclared.intent ?? ""}:${snapshot.agentDeclared.lastEvent ?? ""}`
226
+ : "",
227
+ declaredDrift: snapshot.declaredDriftFiles?.join(",") ?? "",
163
228
  });
164
229
  }
165
- function renderSupervisionSummary(snapshot) {
230
+ function renderSupervisionSummary(snapshot, options) {
166
231
  const lines = [];
167
- lines.push("AgentBridge supervision:");
168
- lines.push(`Current run: ${snapshot.workSessionId}${snapshot.changeRequestId ? ` / task ${snapshot.changeRequestId}` : ""}`);
232
+ lines.push(options?.compact ? "AgentBridge watch:" : "AgentBridge supervision:");
233
+ if (!options?.compact) {
234
+ lines.push(`Current run: ${snapshot.workSessionId}${snapshot.changeRequestId ? ` / task ${snapshot.changeRequestId}` : ""}`);
235
+ }
236
+ const declared = snapshot.agentDeclared;
237
+ if (declared) {
238
+ const intentLabel = declared.intent?.trim() || declared.summary?.trim() || "(no intent text)";
239
+ const eventSuffix = declared.lastEvent && declared.updatedAt
240
+ ? ` [${declared.lastEvent} @ ${formatMcpEventTime(declared.updatedAt)}]`
241
+ : declared.lastEvent
242
+ ? ` [${declared.lastEvent}]`
243
+ : "";
244
+ lines.push(`Agent (MCP): ${intentLabel}${eventSuffix}`);
245
+ if (declared.claimedPaths.length > 0) {
246
+ const scopePreview = declared.claimedPaths.length > 6
247
+ ? `${declared.claimedPaths.slice(0, 6).join(", ")} (+${declared.claimedPaths.length - 6} more)`
248
+ : declared.claimedPaths.join(", ");
249
+ lines.push(`Declared scope: ${scopePreview}`);
250
+ }
251
+ else {
252
+ lines.push("Declared scope: (none yet)");
253
+ }
254
+ }
255
+ const diskCount = snapshot.diskObserved?.postBaselineFiles.length ?? snapshot.changedFiles.length;
256
+ lines.push(`Files (disk): ${diskCount} changed since session start`);
257
+ if (snapshot.declaredVsObservedDrift === "warn" && snapshot.declaredDriftFiles?.length) {
258
+ const preview = snapshot.declaredDriftFiles.length > 4
259
+ ? `${snapshot.declaredDriftFiles.slice(0, 4).join(", ")} (+${snapshot.declaredDriftFiles.length - 4} more)`
260
+ : snapshot.declaredDriftFiles.join(", ");
261
+ lines.push(`Note: ${snapshot.declaredDriftFiles.length} file(s) outside declared scope (${preview}) — warn only, not blocked`);
262
+ }
169
263
  lines.push(`Changed files: ${snapshot.changedFiles.length}`);
170
264
  lines.push(`Detected work type: ${snapshot.workType}`);
171
265
  lines.push(`Scope: ${snapshot.scopeStatus}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentbridge1/cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "CLI for AgentBridge — structured AI agent work sessions with domain authority and approval gates",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "build": "node build.mjs",
11
11
  "prepare": "npm run build",
12
12
  "prepack": "npm run build",
13
- "smoke": "npm run build && node dist/index.js --help && node dist/index.js mcp --help",
13
+ "smoke": "npm run build && test -f dist/mcp/agentbridge-mcp.js && node dist/index.js --help && node dist/index.js mcp --help",
14
14
  "typecheck": "tsc -p tsconfig.json --noEmit",
15
15
  "test": "vitest run"
16
16
  },
@@ -41,5 +41,8 @@
41
41
  "dependencies": {
42
42
  "chalk": "^5.6.2",
43
43
  "chokidar": "^4.0.3"
44
+ },
45
+ "devDependencies": {
46
+ "esbuild": "^0.25.12"
44
47
  }
45
48
  }