@neurcode-ai/cli 0.19.8 โ†’ 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/api-client.d.ts +4 -0
  2. package/dist/api-client.d.ts.map +1 -1
  3. package/dist/api-client.js.map +1 -1
  4. package/dist/commands/activate.d.ts +6 -0
  5. package/dist/commands/activate.d.ts.map +1 -1
  6. package/dist/commands/activate.js +24 -0
  7. package/dist/commands/activate.js.map +1 -1
  8. package/dist/commands/brain.d.ts.map +1 -1
  9. package/dist/commands/brain.js +114 -17
  10. package/dist/commands/brain.js.map +1 -1
  11. package/dist/commands/runtime-doctor.d.ts.map +1 -1
  12. package/dist/commands/runtime-doctor.js +41 -2
  13. package/dist/commands/runtime-doctor.js.map +1 -1
  14. package/dist/commands/runtime-identity.d.ts.map +1 -1
  15. package/dist/commands/runtime-identity.js +46 -1
  16. package/dist/commands/runtime-identity.js.map +1 -1
  17. package/dist/commands/runtime-sync.d.ts.map +1 -1
  18. package/dist/commands/runtime-sync.js +55 -0
  19. package/dist/commands/runtime-sync.js.map +1 -1
  20. package/dist/commands/session-hook.d.ts +16 -1
  21. package/dist/commands/session-hook.d.ts.map +1 -1
  22. package/dist/commands/session-hook.js +165 -10
  23. package/dist/commands/session-hook.js.map +1 -1
  24. package/dist/commands/session.d.ts +4 -1
  25. package/dist/commands/session.d.ts.map +1 -1
  26. package/dist/commands/session.js +39 -17
  27. package/dist/commands/session.js.map +1 -1
  28. package/dist/index.js +36 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/runtime-build.json +5 -5
  31. package/dist/utils/RelevanceScorer.d.ts.map +1 -1
  32. package/dist/utils/RelevanceScorer.js +16 -9
  33. package/dist/utils/RelevanceScorer.js.map +1 -1
  34. package/dist/utils/agent-session-launcher.d.ts.map +1 -1
  35. package/dist/utils/agent-session-launcher.js +142 -97
  36. package/dist/utils/agent-session-launcher.js.map +1 -1
  37. package/dist/utils/brain-lifecycle.d.ts +62 -0
  38. package/dist/utils/brain-lifecycle.d.ts.map +1 -0
  39. package/dist/utils/brain-lifecycle.js +482 -0
  40. package/dist/utils/brain-lifecycle.js.map +1 -0
  41. package/dist/utils/cli-startup.d.ts.map +1 -1
  42. package/dist/utils/cli-startup.js +20 -0
  43. package/dist/utils/cli-startup.js.map +1 -1
  44. package/dist/utils/command-budget.d.ts +10 -0
  45. package/dist/utils/command-budget.d.ts.map +1 -0
  46. package/dist/utils/command-budget.js +203 -0
  47. package/dist/utils/command-budget.js.map +1 -0
  48. package/dist/utils/mcp-server-pin.d.ts +1 -1
  49. package/dist/utils/mcp-server-pin.js +2 -2
  50. package/dist/utils/runtime-authority.d.ts +27 -0
  51. package/dist/utils/runtime-authority.d.ts.map +1 -0
  52. package/dist/utils/runtime-authority.js +162 -0
  53. package/dist/utils/runtime-authority.js.map +1 -0
  54. package/dist/utils/runtime-companion.d.ts +32 -0
  55. package/dist/utils/runtime-companion.d.ts.map +1 -1
  56. package/dist/utils/runtime-companion.js +57 -2
  57. package/dist/utils/runtime-companion.js.map +1 -1
  58. package/dist/utils/runtime-live.d.ts.map +1 -1
  59. package/dist/utils/runtime-live.js +14 -6
  60. package/dist/utils/runtime-live.js.map +1 -1
  61. package/dist/utils/runtime-outbox.d.ts.map +1 -1
  62. package/dist/utils/runtime-outbox.js +133 -1
  63. package/dist/utils/runtime-outbox.js.map +1 -1
  64. package/dist/utils/runtime-privacy.d.ts.map +1 -1
  65. package/dist/utils/runtime-privacy.js +52 -0
  66. package/dist/utils/runtime-privacy.js.map +1 -1
  67. package/dist/utils/runtime-state.d.ts +7 -35
  68. package/dist/utils/runtime-state.d.ts.map +1 -1
  69. package/dist/utils/runtime-state.js +203 -134
  70. package/dist/utils/runtime-state.js.map +1 -1
  71. package/dist/utils/session-start-transaction.d.ts +31 -0
  72. package/dist/utils/session-start-transaction.d.ts.map +1 -0
  73. package/dist/utils/session-start-transaction.js +207 -0
  74. package/dist/utils/session-start-transaction.js.map +1 -0
  75. package/dist/utils/v0-governance.d.ts +2 -1
  76. package/dist/utils/v0-governance.d.ts.map +1 -1
  77. package/dist/utils/v0-governance.js +271 -11
  78. package/dist/utils/v0-governance.js.map +1 -1
  79. package/package.json +6 -4
@@ -23,6 +23,7 @@ exports.resolveSessionForHook = resolveSessionForHook;
23
23
  exports.normalizeHookFilePathForRepo = normalizeHookFilePathForRepo;
24
24
  exports.hookFilePathCandidates = hookFilePathCandidates;
25
25
  exports.proposedSourceFromHookInput = proposedSourceFromHookInput;
26
+ exports.governanceWasExpected = governanceWasExpected;
26
27
  exports.evaluateNoActiveSessionWrite = evaluateNoActiveSessionWrite;
27
28
  exports.shouldKeepSessionActiveForPendingApproval = shouldKeepSessionActiveForPendingApproval;
28
29
  exports.reconcileTrustedAdapterPosture = reconcileTrustedAdapterPosture;
@@ -46,10 +47,14 @@ const structural_understanding_1 = require("../utils/structural-understanding");
46
47
  const consequence_nudges_1 = require("../utils/consequence-nudges");
47
48
  const agent_guard_supervisor_1 = require("../utils/agent-guard-supervisor");
48
49
  const local_repo_brain_1 = require("../utils/local-repo-brain");
50
+ const runtime_authority_1 = require("../utils/runtime-authority");
51
+ const brain_lifecycle_1 = require("../utils/brain-lifecycle");
49
52
  const proposed_change_analysis_1 = require("../utils/proposed-change-analysis");
50
53
  const repo_intelligence_v2_1 = require("../utils/repo-intelligence-v2");
51
54
  const runtime_companion_1 = require("../utils/runtime-companion");
52
55
  const profile_drift_recovery_1 = require("../utils/profile-drift-recovery");
56
+ const session_start_transaction_1 = require("../utils/session-start-transaction");
57
+ const runtime_state_1 = require("../utils/runtime-state");
53
58
  // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
54
59
  /** Read the full hook JSON from stdin, or return {} on any error. */
55
60
  function readHookInput() {
@@ -584,8 +589,62 @@ function blockContext(input) {
584
589
  };
585
590
  }
586
591
  const NO_ACTIVE_SESSION_SCOPE_SENTINEL = '__neurcode_no_active_session_scope__';
592
+ /**
593
+ * Whether governance was previously established for this repository โ€” a built profile, an
594
+ * active-session pointer, or any persisted session record. Used to distinguish ordinary
595
+ * first-run use (advisory) from a runtime that was expected to govern but is currently
596
+ * unavailable (fail closed). Dynamic โ€” derived from on-disk governance artifacts only, no
597
+ * hardcoded repository directory names.
598
+ */
599
+ function governanceWasExpected(repoRoot) {
600
+ try {
601
+ if ((0, fs_1.existsSync)((0, v0_governance_1.profilePath)(repoRoot)))
602
+ return true;
603
+ if ((0, fs_1.existsSync)((0, path_1.join)(repoRoot, '.neurcode', 'active-session.json')))
604
+ return true;
605
+ const sessions = (0, path_1.join)(repoRoot, '.neurcode', 'sessions');
606
+ return (0, fs_1.existsSync)(sessions) && (0, fs_1.readdirSync)(sessions).length > 0;
607
+ }
608
+ catch {
609
+ return false;
610
+ }
611
+ }
612
+ function emptyNoSessionBoundary(filePath) {
613
+ return (0, governance_runtime_1.checkFileBoundary)({
614
+ filePath,
615
+ allowedGlobs: [NO_ACTIVE_SESSION_SCOPE_SENTINEL],
616
+ ownershipRules: [],
617
+ sensitiveGlobs: [],
618
+ approvalRequiredGlobs: [],
619
+ approvedPaths: [],
620
+ approvalGrants: [],
621
+ scopeMode: 'explicit',
622
+ localMode: 'strict',
623
+ });
624
+ }
587
625
  function evaluateNoActiveSessionWrite(repoRoot, filePath) {
588
- const profile = (0, v0_governance_1.ensureFreshGovernanceProfile)(repoRoot).profile;
626
+ let profile;
627
+ try {
628
+ profile = (0, v0_governance_1.ensureFreshGovernanceProfile)(repoRoot).profile;
629
+ }
630
+ catch {
631
+ // P0-E: the governance runtime is expected (this hook is installed) but the profile
632
+ // cannot be evaluated. If governance was previously established, FAIL CLOSED โ€” a
633
+ // protected path must never pass merely because session creation / the runtime failed.
634
+ // First-run repos (no prior governance) stay advisory to avoid blocking ordinary use.
635
+ const assessment = (0, runtime_state_1.classifyRuntimeState)(repoRoot);
636
+ const expected = assessment.governanceExpected;
637
+ return {
638
+ block: expected,
639
+ filePath,
640
+ result: emptyNoSessionBoundary(filePath),
641
+ runtimeState: expected ? assessment.state : 'installed_not_activated',
642
+ message: expected
643
+ ? `โธ Neurcode: the governance runtime is unavailable and cannot verify whether ${filePath} is a protected path, but governance is expected for this repository. Failing closed. Run exactly: ${assessment.recoveryCommand}.`
644
+ : `No governance profile yet at ${repoRoot}; ${filePath} is allowed advisory-only until a governed session establishes one.`,
645
+ };
646
+ }
647
+ const assessment = (0, runtime_state_1.classifyRuntimeState)(repoRoot);
589
648
  const result = (0, governance_runtime_1.checkFileBoundary)({
590
649
  filePath,
591
650
  allowedGlobs: [NO_ACTIVE_SESSION_SCOPE_SENTINEL],
@@ -598,14 +657,19 @@ function evaluateNoActiveSessionWrite(repoRoot, filePath) {
598
657
  localMode: 'strict',
599
658
  });
600
659
  const protectedPath = result.isApprovalRequired || result.isSensitive || result.owners.length > 0;
660
+ const enforcementPaused = assessment.state === 'enforcement_paused';
601
661
  const ownerNote = result.owners.length ? ` Owners: ${result.owners.join(', ')}.` : '';
602
- const message = protectedPath
603
- ? `โธ Neurcode: no active governed session is running, so protected path ${filePath} cannot be checked or approved safely.${ownerNote} Start a governed session with \`neurcode session-hook start\`/agent activation, or run \`neurcode doctor --runtime\` for recovery before retrying.`
604
- : `No active governed session at ${repoRoot}; ${filePath} is not a detected protected path and is allowed advisory-only.`;
662
+ const message = enforcementPaused
663
+ ? `Neurcode enforcement is intentionally paused; ${filePath} is advisory-only. Resume with exactly: ${assessment.recoveryCommand}.`
664
+ : protectedPath
665
+ ? `โธ Neurcode: no active governed session is running, so protected path ${filePath} cannot be checked or approved safely.${ownerNote} Start a governed session with \`neurcode session-hook start\`/agent activation, or run \`neurcode doctor --runtime\` for recovery before retrying.`
666
+ : `No active governed session at ${repoRoot}; ${filePath} is not a detected protected path and is allowed advisory-only.`;
605
667
  return {
606
- block: protectedPath,
668
+ block: protectedPath && !enforcementPaused,
607
669
  filePath,
608
670
  result,
671
+ // Profile is readable but no session is active: governance was expected to run here.
672
+ runtimeState: assessment.state,
609
673
  message,
610
674
  };
611
675
  }
@@ -953,7 +1017,12 @@ async function handleStart(cmdCwd) {
953
1017
  // No text in the prompt โ€” skip session creation (tool-use-only turn)
954
1018
  return;
955
1019
  }
1020
+ (0, session_start_transaction_1.beginSessionStartTransaction)(repoRoot, process.env.NEURCODE_BOUNDED_COMMAND_KEY || 'session_hook_start');
1021
+ // Hoisted so the catch can roll back a session that was created but never activated.
1022
+ let session = null;
1023
+ let sessionActivated = false;
956
1024
  try {
1025
+ (0, session_start_transaction_1.updateSessionStartTransaction)(repoRoot, { phase: 'fingerprinting_profile' });
957
1026
  const profileResult = (0, v0_governance_1.ensureFreshGovernanceProfile)(repoRoot);
958
1027
  let profileFreshness = (0, v0_governance_1.buildProfileFreshnessSignal)(profileResult, profileResult.refreshed ? 'auto_refreshed' : 'none');
959
1028
  if (profileResult.refreshed && profileResult.status !== 'missing') {
@@ -1085,7 +1154,16 @@ async function handleStart(cmdCwd) {
1085
1154
  return;
1086
1155
  }
1087
1156
  const profile = profileResult.profile;
1088
- let session = (0, governance_runtime_1.createSession)(repoRoot, profile, goal.trim());
1157
+ // Transactional start (P0-D): create the durable session record WITHOUT publishing
1158
+ // the active pointer. The pointer is published (activated) only after every
1159
+ // session-shaping step below succeeds, so a start that fails partway leaves no active
1160
+ // pointer and no partial session โ€” it is rolled back in the catch.
1161
+ (0, session_start_transaction_1.updateSessionStartTransaction)(repoRoot, { phase: 'persisting_deferred_session' });
1162
+ session = (0, governance_runtime_1.createSession)(repoRoot, profile, goal.trim(), { activate: false });
1163
+ (0, session_start_transaction_1.updateSessionStartTransaction)(repoRoot, {
1164
+ phase: 'shaping_session',
1165
+ sessionId: session.sessionId,
1166
+ });
1089
1167
  const plannedAtStart = maybeCaptureAgentPlan(repoRoot, session, hookInput);
1090
1168
  if (plannedAtStart)
1091
1169
  session = plannedAtStart;
@@ -1127,13 +1205,36 @@ async function handleStart(cmdCwd) {
1127
1205
  : '');
1128
1206
  const banner = `๐Ÿ”’ Neurcode session ${session.sessionId} ยท ${scopeNote} ยท ` +
1129
1207
  `${session.contract.approvalRequiredGlobs.length} approval-required boundaries`;
1208
+ // Commit: all session-shaping steps succeeded, so publish the active pointer now.
1209
+ (0, session_start_transaction_1.updateSessionStartTransaction)(repoRoot, {
1210
+ phase: 'activating_session',
1211
+ sessionId: session.sessionId,
1212
+ });
1213
+ (0, governance_runtime_1.activateSession)(repoRoot, session.sessionId);
1214
+ sessionActivated = true;
1130
1215
  process.stdout.write(JSON.stringify({ message: banner }) + '\n');
1216
+ // Cloud projection is non-authoritative and must not affect the committed session.
1217
+ (0, session_start_transaction_1.updateSessionStartTransaction)(repoRoot, {
1218
+ phase: 'reconciling_cloud',
1219
+ sessionId: session.sessionId,
1220
+ });
1131
1221
  await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, session, { profileFreshness });
1132
1222
  }
1133
1223
  catch (err) {
1224
+ // A start that failed before activation is rolled back so no partial session and no
1225
+ // dangling active pointer survive (P0-D: failed start leaves nothing behind).
1226
+ if (session && !sessionActivated) {
1227
+ try {
1228
+ (0, governance_runtime_1.removeSession)(repoRoot, session.sessionId);
1229
+ }
1230
+ catch { /* best effort rollback */ }
1231
+ }
1134
1232
  diagnostic(`start failed: ${err instanceof Error ? err.message : String(err)}`);
1135
1233
  // Fail open โ€” don't break the agent turn
1136
1234
  }
1235
+ finally {
1236
+ (0, session_start_transaction_1.clearSessionStartTransaction)(repoRoot);
1237
+ }
1137
1238
  }
1138
1239
  const HARD_PREWRITE_ADAPTERS = new Set(['claude-code-hooks', 'copilot-hooks']);
1139
1240
  /**
@@ -1157,6 +1258,20 @@ async function handleCheck(cmdCwd, trustedAdapterId, trustedTiming) {
1157
1258
  const hookInput = readHookInput();
1158
1259
  const effectiveCwd = cwdFromHookInput(hookInput, cmdCwd);
1159
1260
  const repoRoot = (0, v0_governance_1.resolveRepoRoot)(effectiveCwd);
1261
+ try {
1262
+ (0, runtime_authority_1.assertProtectedRuntimeAuthority)(repoRoot, trustedAdapterId);
1263
+ }
1264
+ catch (error) {
1265
+ denyPreToolUse(error instanceof Error ? error.message : String(error), {
1266
+ blockContext: blockContext({
1267
+ blockType: 'profile_or_runtime_health_block',
1268
+ message: error instanceof Error ? error.message : String(error),
1269
+ runtimeMode: 'strict',
1270
+ nextAction: 'Run `neurcode runtime repair`, restart the agent integration if requested, and retry.',
1271
+ }),
1272
+ });
1273
+ return;
1274
+ }
1160
1275
  (0, hook_heartbeat_1.recordHookHeartbeat)({ repoRoot, eventType: 'check' });
1161
1276
  const requestedSessionId = sessionIdFromHookInput(hookInput);
1162
1277
  const toolName = hookInput['tool_name'] ||
@@ -1199,6 +1314,19 @@ async function handleCheck(cmdCwd, trustedAdapterId, trustedTiming) {
1199
1314
  }
1200
1315
  }
1201
1316
  catch (error) {
1317
+ // P0-E defense in depth: if the protected-path check itself errors and governance
1318
+ // was expected for this repo, fail closed for this path rather than allow it.
1319
+ if (governanceWasExpected(repoRoot)) {
1320
+ denyPreToolUse(`โธ Neurcode could not verify whether ${filePath} is a protected path because the governance runtime errored, and governance is expected for this repository. Failing closed. Run \`neurcode runtime repair\`, then start a governed session and retry.`, {
1321
+ blockContext: blockContext({
1322
+ blockType: 'profile_or_runtime_health_block',
1323
+ filePath,
1324
+ message: error instanceof Error ? error.message : String(error),
1325
+ runtimeMode: 'strict',
1326
+ nextAction: 'Run `neurcode runtime repair`, then start a governed session and retry this path.',
1327
+ }),
1328
+ });
1329
+ }
1202
1330
  diagnostic(`no-active-session protected-path check skipped: ${error instanceof Error ? error.message : String(error)}`);
1203
1331
  }
1204
1332
  }
@@ -1974,6 +2102,13 @@ async function handleFinish(cmdCwd) {
1974
2102
  if (supervisorStop.signaled) {
1975
2103
  diagnostic(`agent guard supervisor stop requested (pid ${supervisorStop.state?.pid ?? 'unknown'})`);
1976
2104
  }
2105
+ try {
2106
+ const brain = await (0, brain_lifecycle_1.scheduleBrainIndex)(repoRoot, { force: true });
2107
+ diagnostic(`repository Brain refresh ${brain.state}`);
2108
+ }
2109
+ catch (brainError) {
2110
+ diagnostic(`repository Brain refresh scheduling failed: ${brainError instanceof Error ? brainError.message : String(brainError)}`);
2111
+ }
1977
2112
  const blockCount = finished.events.filter((e) => e.type === 'check_block').length;
1978
2113
  const warnCount = finished.events.filter((e) => e.type === 'check_warn').length;
1979
2114
  const unresolvedLine = pendingActionableBlock
@@ -2072,6 +2207,8 @@ function sessionHookCommand(program) {
2072
2207
  const cwd = opts.dir || process.cwd();
2073
2208
  const repoRoot = (0, v0_governance_1.resolveRepoRoot)(cwd);
2074
2209
  try {
2210
+ // 1) Authoritative, durable local approval. If this throws, NOTHING is approved
2211
+ // and the outer catch reports a real failure (exit 1, no "Approved" output).
2075
2212
  const result = (0, governance_runtime_1.approveSession)(repoRoot, subOpts.path, {
2076
2213
  reason: subOpts.reason,
2077
2214
  sessionId: subOpts.sessionId,
@@ -2079,8 +2216,26 @@ function sessionHookCommand(program) {
2079
2216
  ttlMs: subOpts.expiry === false || subOpts.expiresAt ? undefined : parseDurationMs(subOpts.expiresIn),
2080
2217
  source: 'local_cli',
2081
2218
  });
2219
+ // 2) Cloud reconcile is NON-AUTHORITATIVE. A reconcile failure must never be
2220
+ // reported as a failed approval (Apache Airflow dogfood P0-C: the misleading
2221
+ // "โœ… Approved โ€ฆ approval failed" sequence). Surface it as a distinct,
2222
+ // non-fatal status and keep exit 0 โ€” the local approval is already durable.
2223
+ let reconcile = { ok: true };
2224
+ try {
2225
+ const session = (0, governance_runtime_1.loadSession)(repoRoot, result.sessionId);
2226
+ if (session) {
2227
+ const published = await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, session);
2228
+ reconcile = { ok: published.ok, error: published.error, pending: published.pending };
2229
+ }
2230
+ }
2231
+ catch (reconcileErr) {
2232
+ reconcile = {
2233
+ ok: false,
2234
+ error: reconcileErr instanceof Error ? reconcileErr.message : String(reconcileErr),
2235
+ };
2236
+ }
2082
2237
  if (subOpts.json) {
2083
- process.stdout.write(JSON.stringify({ ok: true, ...result }, null, 2) + '\n');
2238
+ process.stdout.write(JSON.stringify({ ok: true, ...result, cloudReconcile: reconcile }, null, 2) + '\n');
2084
2239
  }
2085
2240
  else {
2086
2241
  process.stdout.write([
@@ -2089,10 +2244,10 @@ function sessionHookCommand(program) {
2089
2244
  ` Expires: ${result.expiresAt || 'session end'}`,
2090
2245
  ` All approved paths: ${result.approvedPaths.join(', ')}`,
2091
2246
  ].join('\n') + '\n');
2247
+ if (!reconcile.ok) {
2248
+ process.stderr.write(`[neurcode] note: approval is durable locally; cloud reconcile deferred${reconcile.error ? ` (${reconcile.error})` : ''}\n`);
2249
+ }
2092
2250
  }
2093
- const session = (0, governance_runtime_1.loadSession)(repoRoot, result.sessionId);
2094
- if (session)
2095
- await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, session);
2096
2251
  }
2097
2252
  catch (err) {
2098
2253
  const msg = err instanceof Error ? err.message : String(err);