@neurcode-ai/cli 0.20.4 → 0.20.5

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.
@@ -50,6 +50,10 @@ exports.localGovernanceStatusCommand = localGovernanceStatusCommand;
50
50
  exports.resetStaleGovernanceSessionCommand = resetStaleGovernanceSessionCommand;
51
51
  exports.replanGovernanceSessionCommand = replanGovernanceSessionCommand;
52
52
  exports.decideGovernanceReplanCommand = decideGovernanceReplanCommand;
53
+ exports.viewPlanCommand = viewPlanCommand;
54
+ exports.showPlanModeCommand = showPlanModeCommand;
55
+ exports.freezePlanCommand = freezePlanCommand;
56
+ exports.unfreezePlanCommand = unfreezePlanCommand;
53
57
  exports.approveGovernanceSessionCommand = approveGovernanceSessionCommand;
54
58
  exports.showGovernanceObligationsCommand = showGovernanceObligationsCommand;
55
59
  exports.waiveGovernanceObligationCommand = waiveGovernanceObligationCommand;
@@ -498,6 +502,9 @@ function buildLocalGovernanceStatus(options = {}) {
498
502
  profileFreshness,
499
503
  scopeMode: session.contract.scopeMode,
500
504
  planCoherenceMode: session.contract.planCoherenceMode ?? 'warn',
505
+ planMode: session.contract.planMode ?? governance_runtime_1.DEFAULT_PLAN_CONTROL_MODE,
506
+ planFrozen: (0, governance_runtime_1.derivePlanPhase)(session.contract) === 'implementation',
507
+ planPhase: (0, governance_runtime_1.derivePlanPhase)(session.contract),
501
508
  agentPlan: session.contract.agentPlan ?? null,
502
509
  agentPlanRevision: typeof session.contract.agentPlanRevision === 'number'
503
510
  ? session.contract.agentPlanRevision
@@ -567,7 +574,12 @@ function localGovernanceStatusCommand(options = {}) {
567
574
  console.log(chalk.yellow(`Recover: ${profile_drift_recovery_1.PROFILE_DRIFT_RECOVERY_COMMAND} ` +
568
575
  `(--force abandons unresolved operator state${activeStatus.profileFreshness.unresolvedHumanDecisions ? '; unresolved decisions are present' : ''})`));
569
576
  }
570
- console.log(`Plan: ${chalk.white(activeStatus.planCoherenceMode)}${activeStatus.agentPlanRevision ? chalk.dim(` · rev ${activeStatus.agentPlanRevision}`) : ''}`);
577
+ console.log(`Plan: ${chalk.white(activeStatus.planMode)} mode · ` +
578
+ `${activeStatus.planFrozen ? chalk.cyan('frozen') : chalk.dim('open')}` +
579
+ `${activeStatus.agentPlanRevision ? chalk.dim(` · rev ${activeStatus.agentPlanRevision}`) : chalk.dim(' · no plan')}` +
580
+ chalk.dim(` · coherence ${activeStatus.planCoherenceMode}`));
581
+ console.log(chalk.dim(` ${(0, governance_runtime_1.describePlanControlMode)(activeStatus.planMode).headline}`));
582
+ console.log(chalk.dim(` View: neurcode session plan · Freeze: neurcode session plan freeze`));
571
583
  console.log(`Agent: ${chalk.white(activeStatus.agentInvocation.status.replace(/_/g, ' '))}` +
572
584
  chalk.dim(` · score ${activeStatus.agentInvocation.score}`) +
573
585
  chalk.dim(` · checks ${activeStatus.agentInvocation.preWriteCheckCount}`));
@@ -583,7 +595,7 @@ function localGovernanceStatusCommand(options = {}) {
583
595
  chalk.dim(` · checks ${activeStatus.agentSupervisor.state?.evaluationCount ?? 0}`));
584
596
  }
585
597
  if (activeStatus.agentPlan?.summary) {
586
- console.log(`Plan: ${chalk.white(truncate(activeStatus.agentPlan.summary))}`);
598
+ console.log(`Summary: ${chalk.white(truncate(activeStatus.agentPlan.summary))}`);
587
599
  }
588
600
  if (activeStatus.agentInvocation.gaps[0]) {
589
601
  console.log(`Next: ${chalk.yellow(activeStatus.agentInvocation.nextAction)}`);
@@ -966,6 +978,178 @@ async function decideGovernanceReplanCommand(options = {}) {
966
978
  process.exitCode = 1;
967
979
  }
968
980
  }
981
+ // ── Plan negotiation UX (view / mode / freeze / unfreeze) ─────────────────────
982
+ function loadPlanSession(repoRoot, options) {
983
+ return options.sessionId
984
+ ? (0, governance_runtime_1.loadSession)(repoRoot, options.sessionId)
985
+ : (0, governance_runtime_1.loadActiveSession)(repoRoot);
986
+ }
987
+ function printNoActivePlanSession(options, repoRoot) {
988
+ if (options.json) {
989
+ console.log(JSON.stringify({ ok: false, repoRoot, error: 'no active governance session' }, null, 2));
990
+ }
991
+ else {
992
+ (0, messages_1.printError)('No Active Session', 'No active in-flow governance session found.', [
993
+ 'Start a governed task first (e.g. `neurcode run claude --goal "<task>"`), then re-run this command.',
994
+ ]);
995
+ }
996
+ process.exitCode = 1;
997
+ }
998
+ /** `neurcode session plan` — view the active plan, its mode, and freeze state. */
999
+ function viewPlanCommand(options = {}) {
1000
+ const repoRoot = (0, v0_governance_1.resolveRepoRoot)(options.dir || process.cwd());
1001
+ const session = loadPlanSession(repoRoot, options);
1002
+ if (!session) {
1003
+ printNoActivePlanSession(options, repoRoot);
1004
+ return;
1005
+ }
1006
+ const view = (0, governance_runtime_1.buildPlanNegotiationView)(session);
1007
+ if (options.json) {
1008
+ console.log(JSON.stringify({ ok: true, repoRoot, ...view }, null, 2));
1009
+ return;
1010
+ }
1011
+ console.log('');
1012
+ console.log(chalk.bold('Active plan'));
1013
+ console.log(chalk.dim('-'.repeat(72)));
1014
+ console.log(`Session: ${chalk.white(view.sessionId)} ${session.status === 'active' ? chalk.green('active') : chalk.dim(session.status)}`);
1015
+ console.log(`Mode: ${chalk.white(view.planMode)} · ${view.frozen ? chalk.cyan('frozen') : chalk.dim('open (planning)')}${view.frozenExplicit ? '' : chalk.dim(' · implicit')}`);
1016
+ console.log(chalk.dim(` ${view.planModeDescription.headline}`));
1017
+ if (view.frozen) {
1018
+ console.log(chalk.dim(` ${view.planModeDescription.afterFreeze}`));
1019
+ }
1020
+ else {
1021
+ console.log(chalk.dim(` ${view.planModeDescription.planningPhase}`));
1022
+ }
1023
+ if (view.frozenExplicit && view.frozenAt) {
1024
+ console.log(chalk.dim(`Frozen: rev ${view.frozenRevision ?? '?'} at ${view.frozenAt}${view.frozenBy ? ` by ${view.frozenBy}` : ''}`));
1025
+ }
1026
+ if (view.hasPlan) {
1027
+ console.log(`Plan: ${chalk.white(`rev ${view.activePlanRevision}`)}${view.planVersions > 1 ? chalk.dim(` · ${view.planVersions} versions`) : ''}`);
1028
+ if (view.summary)
1029
+ console.log(`Summary: ${chalk.white(truncate(view.summary))}`);
1030
+ if (view.steps.length) {
1031
+ console.log(chalk.bold('Steps'));
1032
+ for (const step of view.steps.slice(0, 8))
1033
+ console.log(chalk.dim(` • ${truncate(step)}`));
1034
+ }
1035
+ if (view.expectedFiles.length)
1036
+ console.log(`Files: ${chalk.dim(compactList(view.expectedFiles, 12))}`);
1037
+ if (view.expectedGlobs.length)
1038
+ console.log(`Globs: ${chalk.dim(compactList(view.expectedGlobs, 12))}`);
1039
+ }
1040
+ else {
1041
+ console.log(chalk.yellow('Plan: none captured yet — the agent has not exposed a plan.'));
1042
+ console.log(chalk.dim(' Capture/extend via `neurcode session replan --plan "<plan>"` or MCP `neurcode_session_replan`.'));
1043
+ }
1044
+ console.log(`Signals: ${chalk.dim(`${view.pendingAmendments.length} pending amendment(s) · ${view.driftWarningCount} drift warn · ${view.blockedBoundaryCount} block(s) · ${view.approvedPaths.length} approved path(s)`)}`);
1045
+ if (view.pendingAmendments.length > 0) {
1046
+ const pending = view.pendingAmendments[0];
1047
+ console.log(chalk.yellow(`Re-plan: ${pending.proposalId} (${pending.risk} risk) — accept with \`neurcode session replan-decide --proposal-id ${pending.proposalId} --decision accept\``));
1048
+ }
1049
+ console.log('');
1050
+ console.log(chalk.dim(`Freeze: neurcode session plan freeze · Unfreeze: neurcode session plan unfreeze`));
1051
+ console.log(chalk.dim(`Amend: neurcode session replan --add-file <path> · Approve: neurcode session approve --path <file>`));
1052
+ console.log('');
1053
+ }
1054
+ /** `neurcode session plan mode` — show + explain the active plan control mode. */
1055
+ function showPlanModeCommand(options = {}) {
1056
+ const repoRoot = (0, v0_governance_1.resolveRepoRoot)(options.dir || process.cwd());
1057
+ const session = loadPlanSession(repoRoot, options);
1058
+ // Mode is a repo policy; explain all three either way so help is useful even
1059
+ // without an active session.
1060
+ const activeMode = session ? (session.contract.planMode ?? governance_runtime_1.DEFAULT_PLAN_CONTROL_MODE) : null;
1061
+ const frozen = session ? (0, governance_runtime_1.derivePlanPhase)(session.contract) === 'implementation' : null;
1062
+ const modes = ['observe', 'advise', 'enforce_after_freeze'];
1063
+ if (options.json) {
1064
+ console.log(JSON.stringify({
1065
+ ok: true,
1066
+ repoRoot,
1067
+ activeMode,
1068
+ frozen,
1069
+ defaultMode: governance_runtime_1.DEFAULT_PLAN_CONTROL_MODE,
1070
+ modes: modes.map((mode) => (0, governance_runtime_1.describePlanControlMode)(mode)),
1071
+ }, null, 2));
1072
+ return;
1073
+ }
1074
+ console.log('');
1075
+ console.log(chalk.bold('Plan control mode'));
1076
+ console.log(chalk.dim('-'.repeat(72)));
1077
+ if (activeMode) {
1078
+ console.log(`Active: ${chalk.white(activeMode)} · ${frozen ? chalk.cyan('frozen') : chalk.dim('open (planning)')}`);
1079
+ }
1080
+ else {
1081
+ console.log(chalk.yellow('No active session — showing how each mode behaves.'));
1082
+ }
1083
+ console.log('');
1084
+ for (const mode of modes) {
1085
+ const description = (0, governance_runtime_1.describePlanControlMode)(mode);
1086
+ const marker = mode === activeMode ? chalk.green('●') : chalk.dim('○');
1087
+ console.log(`${marker} ${chalk.white(mode)}${mode === governance_runtime_1.DEFAULT_PLAN_CONTROL_MODE ? chalk.dim(' (default)') : ''}`);
1088
+ console.log(chalk.dim(` ${description.headline}`));
1089
+ console.log(chalk.dim(` Planning: ${description.planningPhase}`));
1090
+ console.log(chalk.dim(` After freeze: ${description.afterFreeze}`));
1091
+ }
1092
+ console.log('');
1093
+ console.log(chalk.dim('Set the mode in .neurcode/governance.json ("planMode") — see `neurcode bootstrap-policy`.'));
1094
+ console.log(chalk.dim('Freeze the plan with `neurcode session plan freeze` to start enforcement.'));
1095
+ console.log('');
1096
+ }
1097
+ async function runPlanFreezeCommand(freeze, options = {}) {
1098
+ const repoRoot = (0, v0_governance_1.resolveRepoRoot)(options.dir || process.cwd());
1099
+ try {
1100
+ const result = freeze
1101
+ ? (0, governance_runtime_1.freezePlan)(repoRoot, { sessionId: options.sessionId, by: options.by, reason: options.reason })
1102
+ : (0, governance_runtime_1.unfreezePlan)(repoRoot, { sessionId: options.sessionId, by: options.by, reason: options.reason });
1103
+ const session = (0, governance_runtime_1.loadSession)(repoRoot, result.sessionId);
1104
+ if (session)
1105
+ await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, session);
1106
+ if (options.json) {
1107
+ console.log(JSON.stringify({ ok: true, repoRoot, ...result }, null, 2));
1108
+ return;
1109
+ }
1110
+ console.log('');
1111
+ const description = (0, governance_runtime_1.describePlanControlMode)(result.planMode);
1112
+ if (freeze) {
1113
+ console.log(result.changed
1114
+ ? chalk.green(`Plan frozen at revision ${result.activePlanRevision}.`)
1115
+ : chalk.dim('Plan was already frozen.'));
1116
+ console.log(chalk.dim(`Mode: ${result.planMode} — ${description.afterFreeze}`));
1117
+ if (result.planMode === 'enforce_after_freeze') {
1118
+ console.log(chalk.dim(`Now: writes outside the ${result.planFileCount} planned file(s) block until you amend the plan or approve the exact path.`));
1119
+ }
1120
+ console.log(chalk.dim('Unfreeze with `neurcode session plan unfreeze` to reopen planning.'));
1121
+ }
1122
+ else {
1123
+ console.log(result.changed
1124
+ ? chalk.green('Plan unfrozen — reopened for planning.')
1125
+ : chalk.dim('Plan was already open for planning.'));
1126
+ console.log(chalk.dim(`Mode: ${result.planMode} — ${description.planningPhase}`));
1127
+ console.log(chalk.dim('Plan-drift blocking is suspended; credential/secret guards remain in force.'));
1128
+ console.log(chalk.dim('Re-freeze with `neurcode session plan freeze` to resume enforcement.'));
1129
+ }
1130
+ console.log('');
1131
+ }
1132
+ catch (error) {
1133
+ const message = error instanceof Error ? error.message : String(error);
1134
+ if (options.json) {
1135
+ console.log(JSON.stringify({ ok: false, repoRoot, error: message }, null, 2));
1136
+ }
1137
+ else {
1138
+ (0, messages_1.printError)(freeze ? 'Freeze Plan Failed' : 'Unfreeze Plan Failed', message, [
1139
+ 'Start a governed task first, then retry. Use --session-id to target a specific session.',
1140
+ ]);
1141
+ }
1142
+ process.exitCode = 1;
1143
+ }
1144
+ }
1145
+ /** `neurcode session plan freeze` — freeze the active plan. */
1146
+ async function freezePlanCommand(options = {}) {
1147
+ await runPlanFreezeCommand(true, options);
1148
+ }
1149
+ /** `neurcode session plan unfreeze` — reopen the active plan for planning. */
1150
+ async function unfreezePlanCommand(options = {}) {
1151
+ await runPlanFreezeCommand(false, options);
1152
+ }
969
1153
  async function approveGovernanceSessionCommand(options = {}) {
970
1154
  const path = options.path;
971
1155
  if (!path) {