@virsanghavi/axis-server 1.1.2 → 1.3.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.
@@ -23,40 +23,35 @@
23
23
 
24
24
  ---
25
25
 
26
- ## Agent Behavioral Norms
26
+ ## Agent Behavioral Norms (MANDATORY)
27
27
 
28
- ### Plan Before WriteThe Core Invariant
28
+ These norms apply to all AI coding agents. They are not optional or suggestive execute them autonomously.
29
29
 
30
- **No agent writes code unless it either owns a file lock OR has explicitly declined the job board for a scoped reason.**
30
+ ### Autonomous Execution
31
31
 
32
- On non-trivial tasks (2+ files, new features, refactors):
33
- 1. Break work into jobs`post_job`
34
- 2. Claim before editing `claim_next_job`
35
- 3. Lock before writing → `propose_file_access` with a **descriptive intent**
36
- 4. Complete when done → `complete_job` with outcome
32
+ - **NEVER** wait for the user to say "use Axis" or "post a job" or "lock the file." Just do it.
33
+ - **NEVER** pause after analysis to ask permission to proceed. Identify issues start fixing immediately.
34
+ - **NEVER** leave locks or jobs dangling. Complete jobs and finalize sessions without being told.
35
+ - **IMMEDIATELY** call `get_project_soul` as the first action in any session.
37
36
 
38
- Direct edits without a job are allowed only for:
39
- - Single-line fixes, typos, config tweaks
40
- - Clearly scoped changes the user asked for directly
37
+ ### Plan Before Write
41
38
 
42
- ### Force Unlock Policy
39
+ **No agent writes code unless it either owns a file lock OR has explicitly declined the job board for a scoped reason.**
43
40
 
44
- `force_unlock` is a **last resort, not a convenience tool.**
41
+ On non-trivial tasks (2+ files, new features, refactors):
42
+ 1. `post_job` — break into trackable jobs (do this IMMEDIATELY, not after being asked)
43
+ 2. `claim_next_job` — claim before starting
44
+ 3. `propose_file_access` — lock with a **descriptive intent**
45
+ 4. `complete_job` — report outcome when done (this releases the lock)
45
46
 
46
- Rules:
47
- 1. **Never** call `force_unlock` on a file you didn't lock unless:
48
- - The lock has been held for >25 minutes (close to TTL expiry), AND
49
- - The locking agent is clearly not responding or has crashed
50
- 2. **Always** provide a specific reason (e.g. "Agent claude-code crashed 20 minutes ago, lock on auth.ts is blocking progress")
51
- 3. **Never** force-unlock to skip coordination. If another agent holds a lock, work on something else.
52
- 4. Prefer waiting for TTL expiry (30 min) over force-unlocking.
47
+ Skip jobs ONLY for: single-line fixes, typos, config tweaks.
53
48
 
54
49
  ### Lock Hygiene
55
- - Always provide descriptive `intent` when locking (e.g. "Refactor auth middleware to use JWT validation" — not "editing file")
56
- - Release locks early by completing jobs when done
57
- - Call `finalize_session` at end of session to clean up all locks
58
-
59
- ### Shared Memory
60
- - Call `update_shared_context` after completing meaningful steps
61
- - Log decisions, not just actions (e.g. "Chose JWT over session tokens because...")
62
- - Other agents read the notepad in real-timewrite for them
50
+ - Descriptive `intent` when locking (not "editing file").
51
+ - Release locks IMMEDIATELY by completing jobs. Never hold a lock while doing unrelated work.
52
+ - `force_unlock` is a **last resort** only for locks >25 min old from a crashed agent. Always give a reason.
53
+
54
+ ### Session Cleanup (MANDATORY)
55
+ - `complete_job` after EVERY finished task — do not accumulate incomplete jobs.
56
+ - `update_shared_context` after meaningful steps — log decisions, not just actions.
57
+ - `finalize_session` when the user's request is fully complete this is required, not optional.
@@ -827,6 +827,7 @@ var NerveCenter = class {
827
827
  delete this.state.locks[filePath];
828
828
  await this.saveState();
829
829
  }
830
+ this.logLockEvent("FORCE_UNLOCKED", filePath, "admin", void 0, reason);
830
831
  await this.appendToNotepad(`
831
832
  - [FORCE UNLOCK] ${filePath} unlocked by admin. Reason: ${reason}`);
832
833
  return `File ${filePath} has been forcibly unlocked.`;
@@ -849,6 +850,31 @@ ${lockSummary || "No active locks."}
849
850
  ## Live Notepad
850
851
  ${notepad}`;
851
852
  }
853
+ // --- Lock Event Logging ---
854
+ async logLockEvent(eventType, filePath, requestingAgent, blockingAgent, intent) {
855
+ try {
856
+ if (this.contextManager.apiUrl) {
857
+ await this.callCoordination("lock-events", "POST", {
858
+ eventType,
859
+ filePath,
860
+ requestingAgent,
861
+ blockingAgent: blockingAgent || null,
862
+ intent: intent || null
863
+ });
864
+ } else if (this.useSupabase && this.supabase && this._projectId) {
865
+ await this.supabase.from("lock_events").insert({
866
+ project_id: this._projectId,
867
+ event_type: eventType,
868
+ file_path: filePath,
869
+ requesting_agent: requestingAgent,
870
+ blocking_agent: blockingAgent || null,
871
+ intent: intent || null
872
+ });
873
+ }
874
+ } catch (e) {
875
+ logger.warn(`[logLockEvent] Failed to log ${eventType} event: ${e.message}`);
876
+ }
877
+ }
852
878
  // --- Decision & Orchestration ---
853
879
  async proposeFileAccess(agentId, filePath, intent, userPrompt) {
854
880
  return await this.mutex.runExclusive(async () => {
@@ -864,6 +890,7 @@ ${notepad}`;
864
890
  });
865
891
  if (result.status === "DENIED") {
866
892
  logger.info(`[proposeFileAccess] DENIED by server: ${result.message}`);
893
+ this.logLockEvent("BLOCKED", filePath, agentId, result.current_lock?.agent_id, intent);
867
894
  return {
868
895
  status: "REQUIRES_ORCHESTRATION",
869
896
  message: result.message || `File '${filePath}' is locked by another agent`,
@@ -871,6 +898,7 @@ ${notepad}`;
871
898
  };
872
899
  }
873
900
  logger.info(`[proposeFileAccess] GRANTED by server`);
901
+ this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
874
902
  await this.appendToNotepad(`
875
903
  - [LOCK] ${agentId} locked ${filePath}
876
904
  Intent: ${intent}`);
@@ -900,6 +928,7 @@ ${notepad}`;
900
928
  if (error) throw error;
901
929
  const row = Array.isArray(data) ? data[0] : data;
902
930
  if (row && row.status === "DENIED") {
931
+ this.logLockEvent("BLOCKED", filePath, agentId, row.owner_id, intent);
903
932
  return {
904
933
  status: "REQUIRES_ORCHESTRATION",
905
934
  message: `Conflict: File '${filePath}' is locked by '${row.owner_id}'`,
@@ -911,6 +940,7 @@ ${notepad}`;
911
940
  }
912
941
  };
913
942
  }
943
+ this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
914
944
  await this.appendToNotepad(`
915
945
  - [LOCK] ${agentId} locked ${filePath}
916
946
  Intent: ${intent}`);
@@ -923,6 +953,7 @@ ${notepad}`;
923
953
  if (existing) {
924
954
  const isStale = Date.now() - existing.timestamp > this.lockTimeout;
925
955
  if (!isStale && existing.agentId !== agentId) {
956
+ this.logLockEvent("BLOCKED", filePath, agentId, existing.agentId, intent);
926
957
  return {
927
958
  status: "REQUIRES_ORCHESTRATION",
928
959
  message: `Conflict: File '${filePath}' is currently locked by '${existing.agentId}'`,
@@ -932,6 +963,7 @@ ${notepad}`;
932
963
  }
933
964
  this.state.locks[filePath] = { agentId, filePath, intent, userPrompt, timestamp: Date.now() };
934
965
  await this.saveState();
966
+ this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
935
967
  await this.appendToNotepad(`
936
968
  - [LOCK] ${agentId} locked ${filePath}
937
969
  Intent: ${intent}`);
@@ -1254,23 +1286,26 @@ can work on the same codebase without conflicts.
1254
1286
  - Tailwind CSS for styling; no raw CSS unless unavoidable.
1255
1287
  - Functional React components; prefer server components in Next.js App Router.
1256
1288
 
1257
- ## Agent Behavioral Norms
1289
+ ## Agent Behavioral Norms (MANDATORY)
1290
+
1291
+ ### Autonomous Execution
1292
+ Agents MUST execute the full Axis workflow without being told:
1293
+ - NEVER wait for the user to say "use Axis" or "post a job." Just do it.
1294
+ - NEVER pause after analysis to ask permission. Identify issues \u2192 start fixing immediately.
1295
+ - NEVER leave locks or jobs dangling. Complete jobs and finalize sessions automatically.
1296
+ - IMMEDIATELY call get_project_soul as the first action in every session.
1258
1297
 
1259
1298
  ### Plan Before Write
1260
- Every non-trivial task must follow: post_job -> claim_next_job -> propose_file_access -> (edit) -> complete_job.
1261
- Skip only for single-line typo fixes.
1299
+ Every non-trivial task MUST follow: post_job \u2192 claim_next_job \u2192 propose_file_access \u2192 (edit) \u2192 complete_job.
1300
+ Skip ONLY for single-line typo fixes.
1262
1301
 
1263
- ### Force-Unlock Policy
1264
- force_unlock is a LAST RESORT. Before using it:
1265
- 1. Verify the lock is > 25 minutes old.
1266
- 2. Confirm the locking agent is unresponsive.
1267
- 3. Provide a specific reason string.
1268
- Never casually unlock files \u2014 always try propose_file_access first.
1302
+ ### Session Cleanup (MANDATORY)
1303
+ - complete_job IMMEDIATELY after finishing each task.
1304
+ - update_shared_context after meaningful steps.
1305
+ - finalize_session when the user's request is fully complete \u2014 do not wait to be told.
1269
1306
 
1270
- ### Proactive Tool Usage
1271
- Agents must use Axis MCP tools by default \u2014 do not wait for the user to say "use Axis".
1272
- On session start, call get_project_soul or read_context to load project state.
1273
- After significant progress, call update_shared_context.
1307
+ ### Force-Unlock Policy
1308
+ force_unlock is a LAST RESORT \u2014 only for locks >25 min old from a crashed agent. Always give a reason.
1274
1309
  `],
1275
1310
  ["activity.md", "# Activity Log\n\n"]
1276
1311
  ];
@@ -1426,7 +1461,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1426
1461
  // --- Decision & Orchestration ---
1427
1462
  {
1428
1463
  name: "propose_file_access",
1429
- description: "**CRITICAL: REQUEST FILE LOCK**.\n- **MUST** be called *before* editing any file to prevent conflicts with other agents.\n- Checks if another agent currently holds a lock.\n- Returns `GRANTED` if safe to proceed, or `REQUIRES_ORCHESTRATION` if someone else is editing.\n- Usage: Provide your `agentId` (e.g., 'cursor-agent'), `filePath` (absolute), and `intent` (what you are doing).\n- Note: Locks expire after 30 minutes. Use `force_unlock` only if you are certain a lock is stale and blocking progress.",
1464
+ description: "**CRITICAL: REQUEST FILE LOCK** \u2014 call this before EVERY file edit, no exceptions.\n- Checks if another agent currently holds a lock.\n- Returns `GRANTED` if safe to proceed, or `REQUIRES_ORCHESTRATION` if someone else is editing.\n- Usage: Provide your `agentId` (e.g., 'cursor-agent'), `filePath` (absolute), and `intent` (descriptive \u2014 e.g. 'Refactor auth to use JWT', NOT 'editing file').\n- Locks expire after 30 minutes. Use `force_unlock` only as a last resort for crashed agents.",
1430
1465
  inputSchema: {
1431
1466
  type: "object",
1432
1467
  properties: {
@@ -1453,18 +1488,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1453
1488
  // --- Permanent Memory ---
1454
1489
  {
1455
1490
  name: "finalize_session",
1456
- description: "**END OF SESSION HOUSEKEEPING**.\n- Archives the current Live Notepad to a permanent session log.\n- Clears all active locks and completed jobs.\n- Resets the Live Notepad for the next session.\n- Call this when the user says 'we are done' or 'start fresh'.",
1491
+ description: "**MANDATORY SESSION CLEANUP** \u2014 call this automatically when the user's request is fully complete.\n- Archives the current Live Notepad to a permanent session log.\n- Clears all active locks and completed jobs.\n- Resets the Live Notepad for the next session.\n- Do NOT wait for the user to say 'we are done.' When all tasks are finished, call this yourself.",
1457
1492
  inputSchema: { type: "object", properties: {}, required: [] }
1458
1493
  },
1459
1494
  {
1460
1495
  name: "get_project_soul",
1461
- description: "**HIGH-LEVEL INTENT**: Returns the 'Soul' of the project.\n- Combines `context.md`, `conventions.md`, and other core directives into a single prompt.\n- Use this at the *start* of a conversation to ground yourself in the project's reality.",
1496
+ description: "**MANDATORY FIRST CALL**: Returns the project's goals, architecture, conventions, and active state.\n- Combines `context.md`, `conventions.md`, and other core directives into a single prompt.\n- You MUST call this as your FIRST action in every new session or task \u2014 before reading files, before responding to the user, before anything else.\n- Skipping this call means you are working without context and will make wrong decisions.",
1462
1497
  inputSchema: { type: "object", properties: {}, required: [] }
1463
1498
  },
1464
1499
  // --- Job Board (Task Orchestration) ---
1465
1500
  {
1466
1501
  name: "post_job",
1467
- description: "**CREATE TICKET**: Post a new task to the Job Board.\n- Use this when you identify work that needs to be done but *cannot* be done right now (e.g., refactoring, new feature).\n- Supports `dependencies` (list of other Job IDs that must be done first).\n- Priority: low, medium, high, critical.",
1502
+ description: "**CREATE TICKET**: Post a new task to the Job Board.\n- Call this IMMEDIATELY when you receive a non-trivial task (2+ files, new features, refactors). Do not wait to be asked.\n- Break work into trackable jobs BEFORE you start coding.\n- Supports `dependencies` (list of other Job IDs that must be done first).\n- Priority: low, medium, high, critical.",
1468
1503
  inputSchema: {
1469
1504
  type: "object",
1470
1505
  properties: {
@@ -1502,7 +1537,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1502
1537
  },
1503
1538
  {
1504
1539
  name: "claim_next_job",
1505
- description: "**AUTO-ASSIGNMENT**: Ask the Job Board for the next most important task.\n- Respects priority (Critical > High > ...) and dependencies (won't assign a job if its deps aren't done).\n- Returns the Job object if successful, or 'NO_JOBS_AVAILABLE'.\n- Use this when you are idle and looking for work.",
1540
+ description: "**CLAIM WORK**: Claim the next job from the Job Board before starting it.\n- You MUST claim a job before editing files for that job.\n- Respects priority (Critical > High > ...) and dependencies (won't assign a job if its deps aren't done).\n- Returns the Job object if successful, or 'NO_JOBS_AVAILABLE'.\n- Call this immediately after posting jobs, and again after completing each job to pick up the next one.",
1506
1541
  inputSchema: {
1507
1542
  type: "object",
1508
1543
  properties: {
@@ -1513,7 +1548,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1513
1548
  },
1514
1549
  {
1515
1550
  name: "complete_job",
1516
- description: "**CLOSE TICKET**: Mark a job as done.\n- Requires `outcome` (what was done).\n- If you are not the assigned agent, you must provide the `completionKey`.",
1551
+ description: "**CLOSE TICKET**: Mark a job as done and release file locks.\n- Call this IMMEDIATELY after finishing each job \u2014 do not accumulate completed-but-unclosed jobs.\n- Requires `outcome` (what was done).\n- If you are not the assigned agent, you must provide the `completionKey`.\n- Leaving jobs open holds locks and blocks other agents.",
1517
1552
  inputSchema: {
1518
1553
  type: "object",
1519
1554
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@virsanghavi/axis-server",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "description": "Axis MCP Server CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {