@neurcode-ai/cli 0.19.0 โ 0.19.3
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/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +87 -7
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/ops.d.ts +5 -0
- package/dist/commands/ops.d.ts.map +1 -1
- package/dist/commands/ops.js +32 -2
- package/dist/commands/ops.js.map +1 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +50 -0
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/quickstart.d.ts.map +1 -1
- package/dist/commands/quickstart.js +11 -6
- package/dist/commands/quickstart.js.map +1 -1
- package/dist/commands/runtime-doctor.d.ts.map +1 -1
- package/dist/commands/runtime-doctor.js +36 -1
- package/dist/commands/runtime-doctor.js.map +1 -1
- package/dist/commands/session-hook.d.ts.map +1 -1
- package/dist/commands/session-hook.js +125 -8
- package/dist/commands/session-hook.js.map +1 -1
- package/dist/commands/session.d.ts +18 -0
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +617 -181
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/verify-output.d.ts +2 -0
- package/dist/commands/verify-output.d.ts.map +1 -1
- package/dist/commands/verify-output.js +4 -0
- package/dist/commands/verify-output.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +108 -24
- package/dist/commands/verify.js.map +1 -1
- package/dist/governance/structural-on-diff.d.ts +11 -0
- package/dist/governance/structural-on-diff.d.ts.map +1 -1
- package/dist/governance/structural-on-diff.js +38 -5
- package/dist/governance/structural-on-diff.js.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime-build.json +5 -5
- package/dist/utils/brain-context.d.ts.map +1 -1
- package/dist/utils/brain-context.js +11 -2
- package/dist/utils/brain-context.js.map +1 -1
- package/dist/utils/local-repo-brain.d.ts.map +1 -1
- package/dist/utils/local-repo-brain.js +4 -0
- package/dist/utils/local-repo-brain.js.map +1 -1
- package/dist/utils/profile-drift-recovery.d.ts +40 -0
- package/dist/utils/profile-drift-recovery.d.ts.map +1 -0
- package/dist/utils/profile-drift-recovery.js +235 -0
- package/dist/utils/profile-drift-recovery.js.map +1 -0
- package/dist/utils/proposed-change-analysis.d.ts.map +1 -1
- package/dist/utils/proposed-change-analysis.js +7 -5
- package/dist/utils/proposed-change-analysis.js.map +1 -1
- package/dist/utils/repo-intelligence-v2.d.ts.map +1 -1
- package/dist/utils/repo-intelligence-v2.js +41 -0
- package/dist/utils/repo-intelligence-v2.js.map +1 -1
- package/dist/utils/runtime-companion.d.ts.map +1 -1
- package/dist/utils/runtime-companion.js +9 -1
- package/dist/utils/runtime-companion.js.map +1 -1
- package/dist/utils/structural-understanding.d.ts +2 -2
- package/dist/utils/structural-understanding.d.ts.map +1 -1
- package/dist/utils/structural-understanding.js +1 -1
- package/dist/utils/structural-understanding.js.map +1 -1
- package/dist/utils/team-memory-path-hygiene.d.ts +4 -0
- package/dist/utils/team-memory-path-hygiene.d.ts.map +1 -0
- package/dist/utils/team-memory-path-hygiene.js +50 -0
- package/dist/utils/team-memory-path-hygiene.js.map +1 -0
- package/dist/utils/v0-governance.d.ts +19 -1
- package/dist/utils/v0-governance.d.ts.map +1 -1
- package/dist/utils/v0-governance.js +65 -4
- package/dist/utils/v0-governance.js.map +1 -1
- package/package.json +4 -3
package/dist/commands/session.js
CHANGED
|
@@ -43,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
};
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.resolveUnderstandingDiffFiles = resolveUnderstandingDiffFiles;
|
|
46
47
|
exports.buildLocalGovernanceStatus = buildLocalGovernanceStatus;
|
|
47
48
|
exports.localGovernanceStatusCommand = localGovernanceStatusCommand;
|
|
48
49
|
exports.resetStaleGovernanceSessionCommand = resetStaleGovernanceSessionCommand;
|
|
@@ -61,6 +62,7 @@ exports.verifyAIChangeRecordForCli = verifyAIChangeRecordForCli;
|
|
|
61
62
|
exports.structuralUnderstandingCommand = structuralUnderstandingCommand;
|
|
62
63
|
exports.listSessionsCommand = listSessionsCommand;
|
|
63
64
|
exports.endSessionCommand = endSessionCommand;
|
|
65
|
+
exports.endSessionCommandWithDependencies = endSessionCommandWithDependencies;
|
|
64
66
|
exports.sessionStatusCommand = sessionStatusCommand;
|
|
65
67
|
exports.listLocalSessionsCommand = listLocalSessionsCommand;
|
|
66
68
|
exports.currentLocalSessionCommand = currentLocalSessionCommand;
|
|
@@ -84,6 +86,8 @@ const runtime_outbox_1 = require("../utils/runtime-outbox");
|
|
|
84
86
|
const repo_brain_impact_1 = require("../utils/repo-brain-impact");
|
|
85
87
|
const structural_understanding_1 = require("../utils/structural-understanding");
|
|
86
88
|
const agent_guard_supervisor_1 = require("../utils/agent-guard-supervisor");
|
|
89
|
+
const hook_heartbeat_1 = require("../utils/hook-heartbeat");
|
|
90
|
+
const profile_drift_recovery_1 = require("../utils/profile-drift-recovery");
|
|
87
91
|
const node_child_process_1 = require("node:child_process");
|
|
88
92
|
const node_fs_1 = require("node:fs");
|
|
89
93
|
const node_path_1 = require("node:path");
|
|
@@ -173,7 +177,79 @@ function approvalContextFrom(event) {
|
|
|
173
177
|
: event?.filePath,
|
|
174
178
|
};
|
|
175
179
|
}
|
|
176
|
-
|
|
180
|
+
const UNTRACKED_UNDERSTANDING_EXCLUDED_DIRS = new Set([
|
|
181
|
+
'.git',
|
|
182
|
+
'.neurcode',
|
|
183
|
+
'.neurcode-admission',
|
|
184
|
+
'.neurcode-ai-record',
|
|
185
|
+
'.cache',
|
|
186
|
+
'.next',
|
|
187
|
+
'build',
|
|
188
|
+
'cache',
|
|
189
|
+
'coverage',
|
|
190
|
+
'dist',
|
|
191
|
+
'evidence',
|
|
192
|
+
'generated',
|
|
193
|
+
'node_modules',
|
|
194
|
+
'out',
|
|
195
|
+
'vendor',
|
|
196
|
+
]);
|
|
197
|
+
const UNTRACKED_UNDERSTANDING_SOURCE = /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i;
|
|
198
|
+
const UNTRACKED_UNDERSTANDING_GENERATED_FILE = /(?:\.d\.ts|\.map|\.min\.js|\.bundle\.js|\.generated\.[cm]?[jt]sx?)$/i;
|
|
199
|
+
function normalizeUnderstandingPath(value) {
|
|
200
|
+
return value.replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
201
|
+
}
|
|
202
|
+
function eligibleUntrackedUnderstandingPath(repoRoot, value) {
|
|
203
|
+
const normalized = normalizeUnderstandingPath(value);
|
|
204
|
+
if (!normalized || normalized.startsWith('/') || normalized === '..' || normalized.startsWith('../'))
|
|
205
|
+
return null;
|
|
206
|
+
const segments = normalized.split('/');
|
|
207
|
+
if (segments.includes('..') || segments.some((segment) => UNTRACKED_UNDERSTANDING_EXCLUDED_DIRS.has(segment.toLowerCase()))) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (UNTRACKED_UNDERSTANDING_GENERATED_FILE.test(normalized))
|
|
211
|
+
return null;
|
|
212
|
+
const absolutePath = (0, node_path_1.join)(repoRoot, normalized);
|
|
213
|
+
try {
|
|
214
|
+
const stat = (0, node_fs_1.lstatSync)(absolutePath);
|
|
215
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return normalized;
|
|
222
|
+
}
|
|
223
|
+
function untrackedDiffFile(repoRoot, path) {
|
|
224
|
+
let lines = [];
|
|
225
|
+
if (UNTRACKED_UNDERSTANDING_SOURCE.test(path)) {
|
|
226
|
+
const text = (0, node_fs_1.readFileSync)((0, node_path_1.join)(repoRoot, path), 'utf8').replace(/\r\n/g, '\n');
|
|
227
|
+
lines = text ? text.split('\n') : [];
|
|
228
|
+
if (lines.at(-1) === '')
|
|
229
|
+
lines.pop();
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
path,
|
|
233
|
+
changeType: 'add',
|
|
234
|
+
addedLines: lines.length,
|
|
235
|
+
removedLines: 0,
|
|
236
|
+
hunks: lines.length > 0
|
|
237
|
+
? [{
|
|
238
|
+
oldStart: 0,
|
|
239
|
+
oldLines: 0,
|
|
240
|
+
newStart: 1,
|
|
241
|
+
newLines: lines.length,
|
|
242
|
+
lines: lines.map((content, index) => ({
|
|
243
|
+
type: 'added',
|
|
244
|
+
content,
|
|
245
|
+
lineNumber: index + 1,
|
|
246
|
+
})),
|
|
247
|
+
}]
|
|
248
|
+
: [],
|
|
249
|
+
provenance: 'git-untracked',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function resolveUnderstandingDiffFiles(repoRoot, options = {}) {
|
|
177
253
|
const args = ['diff'];
|
|
178
254
|
if (options.staged) {
|
|
179
255
|
args.push('--cached');
|
|
@@ -184,11 +260,27 @@ function resolveUnderstandingDiff(repoRoot, options) {
|
|
|
184
260
|
else {
|
|
185
261
|
args.push('HEAD');
|
|
186
262
|
}
|
|
187
|
-
|
|
263
|
+
const diffText = (0, node_child_process_1.execFileSync)('git', args, {
|
|
264
|
+
cwd: repoRoot,
|
|
265
|
+
encoding: 'utf8',
|
|
266
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
267
|
+
});
|
|
268
|
+
const selected = (0, diff_parser_1.parseDiff)(diffText);
|
|
269
|
+
if (options.staged)
|
|
270
|
+
return selected;
|
|
271
|
+
const untrackedOutput = (0, node_child_process_1.execFileSync)('git', ['ls-files', '--others', '--exclude-standard', '-z'], {
|
|
188
272
|
cwd: repoRoot,
|
|
189
273
|
encoding: 'utf8',
|
|
190
274
|
maxBuffer: 64 * 1024 * 1024,
|
|
191
275
|
});
|
|
276
|
+
const byPath = new Map(selected.map((file) => [normalizeUnderstandingPath(file.path), file]));
|
|
277
|
+
for (const rawPath of untrackedOutput.split('\0')) {
|
|
278
|
+
const path = eligibleUntrackedUnderstandingPath(repoRoot, rawPath);
|
|
279
|
+
if (!path || byPath.has(path))
|
|
280
|
+
continue;
|
|
281
|
+
byPath.set(path, untrackedDiffFile(repoRoot, path));
|
|
282
|
+
}
|
|
283
|
+
return [...byPath.values()].sort((a, b) => normalizeUnderstandingPath(a.path).localeCompare(normalizeUnderstandingPath(b.path)));
|
|
192
284
|
}
|
|
193
285
|
function loadLocalGovernanceSession(repoRoot, sessionId) {
|
|
194
286
|
return sessionId ? (0, governance_runtime_1.loadSession)(repoRoot, sessionId) : (0, governance_runtime_1.loadActiveSession)(repoRoot);
|
|
@@ -251,6 +343,108 @@ function pendingApprovalBlock(session, now = new Date()) {
|
|
|
251
343
|
}
|
|
252
344
|
return null;
|
|
253
345
|
}
|
|
346
|
+
function activePointerInspection(repoRoot) {
|
|
347
|
+
const path = (0, node_path_1.join)(repoRoot, '.neurcode', 'active-session.json');
|
|
348
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
349
|
+
return { state: 'missing', sessionId: null, session: null };
|
|
350
|
+
try {
|
|
351
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
352
|
+
if (parsed.sessionId === null)
|
|
353
|
+
return { state: 'cleared', sessionId: null, session: null };
|
|
354
|
+
if (typeof parsed.sessionId !== 'string' || !parsed.sessionId.trim()) {
|
|
355
|
+
return { state: 'malformed', sessionId: null, session: null };
|
|
356
|
+
}
|
|
357
|
+
const session = (0, governance_runtime_1.loadSession)(repoRoot, parsed.sessionId);
|
|
358
|
+
if (!session || session.status !== 'active') {
|
|
359
|
+
return { state: 'stale', sessionId: parsed.sessionId, session };
|
|
360
|
+
}
|
|
361
|
+
return { state: 'valid', sessionId: parsed.sessionId, session };
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return { state: 'malformed', sessionId: null, session: null };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function validGovernanceSessionRecord(value) {
|
|
368
|
+
if (!value || typeof value !== 'object')
|
|
369
|
+
return false;
|
|
370
|
+
const record = value;
|
|
371
|
+
return (typeof record.sessionId === 'string' &&
|
|
372
|
+
typeof record.profileHash === 'string' &&
|
|
373
|
+
(record.status === 'active' || record.status === 'finished') &&
|
|
374
|
+
Array.isArray(record.events) &&
|
|
375
|
+
Boolean(record.contract && typeof record.contract === 'object'));
|
|
376
|
+
}
|
|
377
|
+
function scanSessionRecords(repoRoot) {
|
|
378
|
+
const directory = (0, governance_runtime_1.sessionsDir)(repoRoot);
|
|
379
|
+
if (!(0, node_fs_1.existsSync)(directory))
|
|
380
|
+
return { active: [], malformed: [] };
|
|
381
|
+
const active = [];
|
|
382
|
+
const malformed = [];
|
|
383
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })
|
|
384
|
+
.filter((item) => item.isFile() && item.name.endsWith('.json') && !item.name.endsWith('.change-record.json'))
|
|
385
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
386
|
+
const relativePath = `.neurcode/sessions/${entry.name}`;
|
|
387
|
+
try {
|
|
388
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(directory, entry.name), 'utf8'));
|
|
389
|
+
if (!validGovernanceSessionRecord(parsed)) {
|
|
390
|
+
malformed.push({ file: relativePath, reasonCode: 'invalid_session_record' });
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (entry.name !== `${parsed.sessionId}.json`) {
|
|
394
|
+
malformed.push({ file: relativePath, reasonCode: 'session_id_filename_mismatch' });
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (parsed.status === 'active')
|
|
398
|
+
active.push(parsed);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
malformed.push({ file: relativePath, reasonCode: 'malformed_json' });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
active: active.sort((a, b) => a.sessionId.localeCompare(b.sessionId)),
|
|
406
|
+
malformed,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function recoveryReasonCode(pointer, sessionId) {
|
|
410
|
+
if (pointer.state === 'valid') {
|
|
411
|
+
return pointer.sessionId === sessionId
|
|
412
|
+
? 'stale_referenced_active_session'
|
|
413
|
+
: 'orphan_unreferenced_active_record';
|
|
414
|
+
}
|
|
415
|
+
if (pointer.state === 'missing')
|
|
416
|
+
return 'orphan_missing_active_pointer';
|
|
417
|
+
if (pointer.state === 'cleared')
|
|
418
|
+
return 'orphan_cleared_active_pointer';
|
|
419
|
+
if (pointer.state === 'malformed')
|
|
420
|
+
return 'orphan_malformed_active_pointer';
|
|
421
|
+
return 'orphan_stale_active_pointer';
|
|
422
|
+
}
|
|
423
|
+
function activeSessionLiveness(repoRoot, sessionId, now) {
|
|
424
|
+
const reasons = [];
|
|
425
|
+
const supervisor = (0, agent_guard_supervisor_1.inspectAgentGuardSupervisor)(repoRoot, sessionId);
|
|
426
|
+
if (supervisor.state?.sessionId === sessionId &&
|
|
427
|
+
supervisor.alive &&
|
|
428
|
+
['running', 'starting', 'stopping'].includes(supervisor.effectiveStatus)) {
|
|
429
|
+
reasons.push('live_supervisor_process');
|
|
430
|
+
}
|
|
431
|
+
const heartbeat = (0, hook_heartbeat_1.readHookHeartbeat)(repoRoot);
|
|
432
|
+
const heartbeatAt = Date.parse(heartbeat?.lastEvent.ts ?? '');
|
|
433
|
+
if (heartbeat?.lastEvent.sessionId === sessionId &&
|
|
434
|
+
Number.isFinite(heartbeatAt) &&
|
|
435
|
+
now.getTime() - heartbeatAt >= 0 &&
|
|
436
|
+
now.getTime() - heartbeatAt <= 5 * 60_000) {
|
|
437
|
+
reasons.push('fresh_hook_heartbeat');
|
|
438
|
+
}
|
|
439
|
+
return reasons;
|
|
440
|
+
}
|
|
441
|
+
function clearInvalidActivePointer(repoRoot, pointer) {
|
|
442
|
+
if (pointer.state === 'valid')
|
|
443
|
+
return;
|
|
444
|
+
const directory = (0, node_path_1.join)(repoRoot, '.neurcode');
|
|
445
|
+
(0, node_fs_1.mkdirSync)(directory, { recursive: true });
|
|
446
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, 'active-session.json'), JSON.stringify({ sessionId: null }, null, 2) + '\n', 'utf8');
|
|
447
|
+
}
|
|
254
448
|
function buildLocalGovernanceStatus(options = {}) {
|
|
255
449
|
const repoRoot = (0, v0_governance_1.resolveRepoRoot)(options.dir || process.cwd());
|
|
256
450
|
const session = loadLocalGovernanceSession(repoRoot, options.sessionId);
|
|
@@ -267,6 +461,19 @@ function buildLocalGovernanceStatus(options = {}) {
|
|
|
267
461
|
};
|
|
268
462
|
}
|
|
269
463
|
const recentEvents = session.events.slice(-10);
|
|
464
|
+
const staleness = (0, v0_governance_1.getProfileStaleness)(repoRoot);
|
|
465
|
+
const profileAction = (0, v0_governance_1.profileFreshnessActionForSession)(staleness, session.profileHash);
|
|
466
|
+
const pendingProfileDecisions = (0, profile_drift_recovery_1.pendingProfileDriftDecisions)(session);
|
|
467
|
+
const profileFreshness = (0, v0_governance_1.buildProfileFreshnessSignal)(staleness, profileAction, {
|
|
468
|
+
sessionProfileHash: session.profileHash,
|
|
469
|
+
...(profileAction === 'session_restart_required'
|
|
470
|
+
? {
|
|
471
|
+
recoveryReason: 'active_session_profile_changed',
|
|
472
|
+
recoveryCommand: profile_drift_recovery_1.PROFILE_DRIFT_RECOVERY_COMMAND,
|
|
473
|
+
unresolvedHumanDecisions: pendingProfileDecisions.length > 0,
|
|
474
|
+
}
|
|
475
|
+
: {}),
|
|
476
|
+
});
|
|
270
477
|
const latestBlock = [...session.events].reverse().find((event) => event.type === 'check_block');
|
|
271
478
|
const latestApprovalContext = approvalContextFrom(latestBlock);
|
|
272
479
|
const suggestedApprovalPath = latestApprovalContext?.suggestedApprovalPath ||
|
|
@@ -280,6 +487,7 @@ function buildLocalGovernanceStatus(options = {}) {
|
|
|
280
487
|
status: session.status,
|
|
281
488
|
goal: session.contract.goal,
|
|
282
489
|
profileHash: session.profileHash,
|
|
490
|
+
profileFreshness,
|
|
283
491
|
scopeMode: session.contract.scopeMode,
|
|
284
492
|
planCoherenceMode: session.contract.planCoherenceMode ?? 'warn',
|
|
285
493
|
agentPlan: session.contract.agentPlan ?? null,
|
|
@@ -341,6 +549,14 @@ function localGovernanceStatusCommand(options = {}) {
|
|
|
341
549
|
console.log(`Session: ${chalk.white(activeStatus.sessionId)} ${activeStatus.active ? chalk.green('active') : chalk.dim(activeStatus.status)}`);
|
|
342
550
|
console.log(`Goal: ${chalk.white(truncate(activeStatus.goal))}`);
|
|
343
551
|
console.log(`Scope: ${chalk.white(activeStatus.scopeMode)}`);
|
|
552
|
+
console.log(`Profile: ${chalk.white(activeStatus.profileFreshness.status)} cache ยท ` +
|
|
553
|
+
`${chalk.white(activeStatus.profileFreshness.sessionCompatibility)} session`);
|
|
554
|
+
if (activeStatus.profileFreshness.sessionCompatibility === 'incompatible') {
|
|
555
|
+
console.log(chalk.yellow(`Hashes: session ${activeStatus.profileHash.slice(0, 12)} ยท ` +
|
|
556
|
+
`current ${activeStatus.profileFreshness.currentProfileHash.slice(0, 12)}`));
|
|
557
|
+
console.log(chalk.yellow(`Recover: ${profile_drift_recovery_1.PROFILE_DRIFT_RECOVERY_COMMAND} ` +
|
|
558
|
+
`(--force abandons unresolved operator state${activeStatus.profileFreshness.unresolvedHumanDecisions ? '; unresolved decisions are present' : ''})`));
|
|
559
|
+
}
|
|
344
560
|
console.log(`Plan: ${chalk.white(activeStatus.planCoherenceMode)}${activeStatus.agentPlanRevision ? chalk.dim(` ยท rev ${activeStatus.agentPlanRevision}`) : ''}`);
|
|
345
561
|
console.log(`Agent: ${chalk.white(activeStatus.agentInvocation.status.replace(/_/g, ' '))}` +
|
|
346
562
|
chalk.dim(` ยท score ${activeStatus.agentInvocation.score}`) +
|
|
@@ -413,7 +629,8 @@ async function resetStaleGovernanceSessionCommand(options = {}) {
|
|
|
413
629
|
const maxAgeMinutes = Number.isFinite(options.maxAgeMinutes)
|
|
414
630
|
? Math.max(0, Number(options.maxAgeMinutes))
|
|
415
631
|
: 120;
|
|
416
|
-
const
|
|
632
|
+
const pointer = activePointerInspection(repoRoot);
|
|
633
|
+
const records = scanSessionRecords(repoRoot);
|
|
417
634
|
const output = (payload, statusCode = 0) => {
|
|
418
635
|
if (options.json) {
|
|
419
636
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -433,92 +650,175 @@ async function resetStaleGovernanceSessionCommand(options = {}) {
|
|
|
433
650
|
}
|
|
434
651
|
process.exitCode = statusCode;
|
|
435
652
|
};
|
|
436
|
-
if (
|
|
653
|
+
if (records.active.length === 0) {
|
|
437
654
|
output({
|
|
438
655
|
ok: true,
|
|
439
656
|
reset: false,
|
|
440
657
|
repoRoot,
|
|
441
658
|
reason: 'no_active_session',
|
|
659
|
+
pointerState: pointer.state,
|
|
660
|
+
malformedRecords: records.malformed,
|
|
442
661
|
message: 'No active in-flow governance session found.',
|
|
443
662
|
});
|
|
444
663
|
return;
|
|
445
664
|
}
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
|
|
665
|
+
const recovered = [];
|
|
666
|
+
const skipped = [];
|
|
667
|
+
for (const active of records.active) {
|
|
668
|
+
const ageMinutes = sessionAgeMinutes(active, now);
|
|
669
|
+
const pending = pendingApprovalBlock(active, now);
|
|
670
|
+
const stale = ageMinutes >= maxAgeMinutes;
|
|
671
|
+
const liveness = activeSessionLiveness(repoRoot, active.sessionId, now);
|
|
672
|
+
const reasonCode = recoveryReasonCode(pointer, active.sessionId);
|
|
673
|
+
if (liveness.length > 0) {
|
|
674
|
+
skipped.push({
|
|
675
|
+
sessionId: active.sessionId,
|
|
676
|
+
reason: 'session_live',
|
|
677
|
+
reasonCode,
|
|
678
|
+
liveness,
|
|
679
|
+
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
680
|
+
});
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (!stale && options.force !== true) {
|
|
684
|
+
skipped.push({
|
|
685
|
+
sessionId: active.sessionId,
|
|
686
|
+
reason: 'session_not_stale',
|
|
687
|
+
reasonCode,
|
|
688
|
+
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
689
|
+
pendingApproval: pending,
|
|
690
|
+
});
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (pending && options.force !== true) {
|
|
694
|
+
skipped.push({
|
|
695
|
+
sessionId: active.sessionId,
|
|
696
|
+
reason: 'pending_approval',
|
|
697
|
+
reasonCode,
|
|
698
|
+
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
699
|
+
filePath: pending.filePath,
|
|
700
|
+
owners: pending.owners,
|
|
701
|
+
suggestedApprovalPath: pending.suggestedApprovalPath,
|
|
702
|
+
});
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
const pointerReferenced = pointer.state === 'valid' && pointer.sessionId === active.sessionId;
|
|
706
|
+
(0, governance_runtime_1.appendEvent)(repoRoot, active.sessionId, {
|
|
707
|
+
type: 'user_decision',
|
|
708
|
+
ts: now.toISOString(),
|
|
709
|
+
decision: pointerReferenced ? 'reset_stale_session' : 'recover_orphaned_session',
|
|
710
|
+
detail: {
|
|
711
|
+
source: 'local_cli',
|
|
712
|
+
recovery: true,
|
|
713
|
+
reasonCode,
|
|
714
|
+
pointerState: pointer.state,
|
|
715
|
+
force: options.force === true,
|
|
716
|
+
maxAgeMinutes,
|
|
717
|
+
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
718
|
+
pendingApproval: pending,
|
|
719
|
+
livenessChecks: ['bounded_event_age', 'guard_supervisor_process', 'hook_heartbeat'],
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
const livePointerPath = (0, node_path_1.join)(repoRoot, '.neurcode', 'active-session.json');
|
|
723
|
+
const preservedLivePointer = pointer.state === 'valid' && pointer.sessionId !== active.sessionId && (0, node_fs_1.existsSync)(livePointerPath)
|
|
724
|
+
? (0, node_fs_1.readFileSync)(livePointerPath, 'utf8')
|
|
725
|
+
: null;
|
|
726
|
+
let finished = null;
|
|
727
|
+
try {
|
|
728
|
+
finished = (0, governance_runtime_1.finishSession)(repoRoot, active.sessionId, { reason: reasonCode });
|
|
729
|
+
}
|
|
730
|
+
finally {
|
|
731
|
+
if (preservedLivePointer !== null) {
|
|
732
|
+
(0, node_fs_1.writeFileSync)(livePointerPath, preservedLivePointer, 'utf8');
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (!finished) {
|
|
736
|
+
skipped.push({ sessionId: active.sessionId, reason: 'finish_failed', reasonCode });
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
const supervisor = (0, agent_guard_supervisor_1.inspectAgentGuardSupervisor)(repoRoot, finished.sessionId);
|
|
740
|
+
if (supervisor.state?.sessionId === finished.sessionId) {
|
|
741
|
+
(0, agent_guard_supervisor_1.stopSupervisorOnSessionCompletion)(repoRoot);
|
|
742
|
+
}
|
|
743
|
+
await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, finished);
|
|
744
|
+
const replay = (0, governance_runtime_1.replaySession)(finished);
|
|
745
|
+
recovered.push({
|
|
746
|
+
sessionId: finished.sessionId,
|
|
747
|
+
previousGoal: finished.contract.goal,
|
|
748
|
+
status: finished.status,
|
|
749
|
+
reasonCode,
|
|
750
|
+
replayHash: finished.replayHash,
|
|
751
|
+
replayVerified: replay.matchesOriginal,
|
|
752
|
+
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
753
|
+
forced: options.force === true,
|
|
754
|
+
recordPath: `.neurcode/sessions/${finished.sessionId}.json`,
|
|
755
|
+
evidencePath: (0, node_path_1.relative)(repoRoot, (0, governance_runtime_1.aiChangeRecordPath)(repoRoot, finished.sessionId)).replace(/\\/g, '/'),
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
if (recovered.length > 0) {
|
|
759
|
+
clearInvalidActivePointer(repoRoot, pointer);
|
|
760
|
+
const first = recovered[0];
|
|
450
761
|
output({
|
|
451
762
|
ok: true,
|
|
452
|
-
reset:
|
|
763
|
+
reset: true,
|
|
453
764
|
repoRoot,
|
|
454
|
-
sessionId:
|
|
455
|
-
|
|
456
|
-
|
|
765
|
+
sessionId: recovered.length === 1 ? first.sessionId : undefined,
|
|
766
|
+
status: recovered.length === 1 ? first.status : 'finished',
|
|
767
|
+
replayHash: recovered.length === 1 ? first.replayHash : undefined,
|
|
768
|
+
ageMinutes: recovered.length === 1 ? first.ageMinutes : undefined,
|
|
457
769
|
maxAgeMinutes,
|
|
458
|
-
|
|
459
|
-
|
|
770
|
+
forced: options.force === true,
|
|
771
|
+
pointerState: pointer.state,
|
|
772
|
+
recoveredCount: recovered.length,
|
|
773
|
+
recovered,
|
|
774
|
+
skipped,
|
|
775
|
+
malformedRecords: records.malformed,
|
|
776
|
+
message: `Recovered ${recovered.length} stale or orphaned active session record${recovered.length === 1 ? '' : 's'}.`,
|
|
460
777
|
});
|
|
461
778
|
return;
|
|
462
779
|
}
|
|
463
|
-
|
|
780
|
+
const primary = skipped.find((item) => item.sessionId === pointer.sessionId) ?? skipped[0];
|
|
781
|
+
if (primary?.reason === 'pending_approval') {
|
|
464
782
|
output({
|
|
465
783
|
ok: false,
|
|
466
784
|
reset: false,
|
|
467
785
|
repoRoot,
|
|
468
|
-
|
|
469
|
-
reason: 'pending_approval',
|
|
470
|
-
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
786
|
+
...primary,
|
|
471
787
|
maxAgeMinutes,
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
suggestedApprovalPath: pending.suggestedApprovalPath,
|
|
788
|
+
pointerState: pointer.state,
|
|
789
|
+
malformedRecords: records.malformed,
|
|
475
790
|
message: 'Active session is waiting on an unresolved approval; refusing to reset without --force.',
|
|
476
791
|
next: [
|
|
477
|
-
`Approve exactly ${
|
|
792
|
+
`Approve exactly ${primary.suggestedApprovalPath} from the dashboard or MCP.`,
|
|
478
793
|
'Or run `neurcode session reset-stale --force` if this is abandoned rehearsal state.',
|
|
479
794
|
],
|
|
480
795
|
}, 2);
|
|
481
796
|
return;
|
|
482
797
|
}
|
|
483
|
-
(
|
|
484
|
-
type: 'user_decision',
|
|
485
|
-
ts: now.toISOString(),
|
|
486
|
-
decision: 'reset_stale_session',
|
|
487
|
-
detail: {
|
|
488
|
-
source: 'local_cli',
|
|
489
|
-
force: options.force === true,
|
|
490
|
-
maxAgeMinutes,
|
|
491
|
-
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
492
|
-
pendingApproval: pending,
|
|
493
|
-
},
|
|
494
|
-
});
|
|
495
|
-
const finished = (0, governance_runtime_1.finishSession)(repoRoot, active.sessionId);
|
|
496
|
-
if (!finished) {
|
|
798
|
+
if (primary?.reason === 'finish_failed') {
|
|
497
799
|
output({
|
|
498
800
|
ok: false,
|
|
499
801
|
reset: false,
|
|
500
802
|
repoRoot,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
803
|
+
...primary,
|
|
804
|
+
pointerState: pointer.state,
|
|
805
|
+
malformedRecords: records.malformed,
|
|
806
|
+
message: `Could not finish active session ${primary.sessionId}.`,
|
|
504
807
|
}, 1);
|
|
505
808
|
return;
|
|
506
809
|
}
|
|
507
|
-
(0, agent_guard_supervisor_1.stopSupervisorOnSessionCompletion)(repoRoot);
|
|
508
|
-
await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, finished);
|
|
509
810
|
output({
|
|
510
811
|
ok: true,
|
|
511
|
-
reset:
|
|
812
|
+
reset: false,
|
|
512
813
|
repoRoot,
|
|
513
|
-
|
|
514
|
-
previousGoal: finished.contract.goal,
|
|
515
|
-
status: finished.status,
|
|
516
|
-
replayHash: finished.replayHash,
|
|
517
|
-
ageMinutes: Number(ageMinutes.toFixed(2)),
|
|
814
|
+
...primary,
|
|
518
815
|
maxAgeMinutes,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
816
|
+
pointerState: pointer.state,
|
|
817
|
+
skipped,
|
|
818
|
+
malformedRecords: records.malformed,
|
|
819
|
+
message: primary?.reason === 'session_live'
|
|
820
|
+
? `Session ${primary.sessionId} has current process or heartbeat liveness evidence and was preserved.`
|
|
821
|
+
: `Active session ${primary?.sessionId} is not stale yet (${Number(primary?.ageMinutes ?? 0).toFixed(1)}m < ${maxAgeMinutes}m).`,
|
|
522
822
|
});
|
|
523
823
|
}
|
|
524
824
|
async function replanGovernanceSessionCommand(options = {}) {
|
|
@@ -1547,9 +1847,9 @@ function structuralUnderstandingCommand(options = {}) {
|
|
|
1547
1847
|
process.exitCode = 1;
|
|
1548
1848
|
return;
|
|
1549
1849
|
}
|
|
1550
|
-
let
|
|
1850
|
+
let diffFiles = [];
|
|
1551
1851
|
try {
|
|
1552
|
-
|
|
1852
|
+
diffFiles = resolveUnderstandingDiffFiles(repoRoot, options);
|
|
1553
1853
|
}
|
|
1554
1854
|
catch (error) {
|
|
1555
1855
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1562,7 +1862,6 @@ function structuralUnderstandingCommand(options = {}) {
|
|
|
1562
1862
|
process.exitCode = 1;
|
|
1563
1863
|
return;
|
|
1564
1864
|
}
|
|
1565
|
-
const diffFiles = diffText.trim() ? (0, diff_parser_1.parseDiff)(diffText) : [];
|
|
1566
1865
|
const profile = (0, v0_governance_1.getProfileStaleness)(repoRoot).currentProfile;
|
|
1567
1866
|
const artifact = (0, structural_understanding_1.buildStructuralUnderstanding)(repoRoot, diffFiles, {
|
|
1568
1867
|
session,
|
|
@@ -1679,157 +1978,294 @@ async function listSessionsCommand(options) {
|
|
|
1679
1978
|
* End a session
|
|
1680
1979
|
*/
|
|
1681
1980
|
async function endSessionCommand(options) {
|
|
1981
|
+
return endSessionCommandWithDependencies(options);
|
|
1982
|
+
}
|
|
1983
|
+
function endSessionOutput(options, payload, exitCode = 0) {
|
|
1984
|
+
if (options.json) {
|
|
1985
|
+
console.log(JSON.stringify({ ...payload, exitCode }, null, 2));
|
|
1986
|
+
}
|
|
1987
|
+
else if (payload.ok === true) {
|
|
1988
|
+
console.log(chalk.green(String(payload.message || 'Session ended.')));
|
|
1989
|
+
if (payload.sessionId)
|
|
1990
|
+
console.log(chalk.dim(`Session: ${payload.sessionId}`));
|
|
1991
|
+
if (payload.replayHash)
|
|
1992
|
+
console.log(chalk.dim(`replayHash: ${payload.replayHash}`));
|
|
1993
|
+
}
|
|
1994
|
+
else {
|
|
1995
|
+
console.error(chalk.red(String(payload.message || payload.error || 'Session end failed.')));
|
|
1996
|
+
const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];
|
|
1997
|
+
for (const candidate of candidates) {
|
|
1998
|
+
const item = candidate;
|
|
1999
|
+
console.error(chalk.dim(` ${item.sessionId || 'unknown'}: ${item.command || ''}`));
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
process.exitCode = exitCode;
|
|
2003
|
+
}
|
|
2004
|
+
async function finishLocalGovernanceSession(repoRoot, session) {
|
|
2005
|
+
if (session.status === 'finished') {
|
|
2006
|
+
return {
|
|
2007
|
+
ok: true,
|
|
2008
|
+
ended: false,
|
|
2009
|
+
mode: 'local',
|
|
2010
|
+
status: 'already_finished',
|
|
2011
|
+
sessionId: session.sessionId,
|
|
2012
|
+
replayHash: session.replayHash || null,
|
|
2013
|
+
message: `Local governance session ${session.sessionId} is already finished.`,
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
(0, governance_runtime_1.appendEvent)(repoRoot, session.sessionId, {
|
|
2017
|
+
type: 'user_decision',
|
|
2018
|
+
ts: new Date().toISOString(),
|
|
2019
|
+
decision: 'local_session_end_requested',
|
|
2020
|
+
message: 'Operator ended the local governance session.',
|
|
2021
|
+
detail: {
|
|
2022
|
+
source: 'local_cli',
|
|
2023
|
+
command: 'session end',
|
|
2024
|
+
},
|
|
2025
|
+
});
|
|
2026
|
+
const finished = (0, governance_runtime_1.finishSession)(repoRoot, session.sessionId, {
|
|
2027
|
+
reason: 'local_session_end_requested',
|
|
2028
|
+
});
|
|
2029
|
+
if (!finished)
|
|
2030
|
+
throw new Error(`Local governance session ${session.sessionId} could not be finished.`);
|
|
2031
|
+
(0, agent_guard_supervisor_1.stopSupervisorOnSessionCompletion)(repoRoot);
|
|
2032
|
+
let liveStatusPublished = true;
|
|
1682
2033
|
try {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
2034
|
+
await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, finished);
|
|
2035
|
+
}
|
|
2036
|
+
catch {
|
|
2037
|
+
liveStatusPublished = false;
|
|
2038
|
+
}
|
|
2039
|
+
const replay = (0, governance_runtime_1.replaySession)(finished);
|
|
2040
|
+
return {
|
|
2041
|
+
ok: true,
|
|
2042
|
+
ended: true,
|
|
2043
|
+
mode: 'local',
|
|
2044
|
+
status: finished.status,
|
|
2045
|
+
sessionId: finished.sessionId,
|
|
2046
|
+
replayHash: finished.replayHash,
|
|
2047
|
+
replayVerified: replay.matchesOriginal,
|
|
2048
|
+
liveStatusPublished,
|
|
2049
|
+
recordPath: `.neurcode/sessions/${finished.sessionId}.json`,
|
|
2050
|
+
evidencePath: (0, node_path_1.relative)(repoRoot, (0, governance_runtime_1.aiChangeRecordPath)(repoRoot, finished.sessionId)).replace(/\\/g, '/'),
|
|
2051
|
+
message: `Local governance session ${finished.sessionId} ended with replay-valid evidence.`,
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
async function endSessionCommandWithDependencies(options, dependencies = {}) {
|
|
2055
|
+
const repoRoot = (0, v0_governance_1.resolveRepoRoot)(options.dir || process.cwd());
|
|
2056
|
+
const isInteractive = dependencies.isInteractive ??
|
|
2057
|
+
(() => Boolean(process.stdin.isTTY && process.stdout.isTTY));
|
|
2058
|
+
const prompt = dependencies.prompt ?? promptUser;
|
|
2059
|
+
try {
|
|
2060
|
+
if (options.sessionId) {
|
|
2061
|
+
const local = (0, governance_runtime_1.loadSession)(repoRoot, options.sessionId);
|
|
2062
|
+
if (local) {
|
|
2063
|
+
endSessionOutput(options, await finishLocalGovernanceSession(repoRoot, local));
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
if (options.local) {
|
|
2067
|
+
endSessionOutput(options, {
|
|
2068
|
+
ok: false,
|
|
2069
|
+
ended: false,
|
|
2070
|
+
mode: 'local',
|
|
2071
|
+
reason: 'local_session_not_found',
|
|
2072
|
+
sessionId: options.sessionId,
|
|
2073
|
+
message: `Local governance session ${options.sessionId} was not found.`,
|
|
2074
|
+
}, 2);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
1686
2077
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
if (
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2078
|
+
else {
|
|
2079
|
+
const records = scanSessionRecords(repoRoot);
|
|
2080
|
+
if (records.active.length === 1) {
|
|
2081
|
+
endSessionOutput(options, await finishLocalGovernanceSession(repoRoot, records.active[0]));
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
if (records.active.length > 1) {
|
|
2085
|
+
if (!isInteractive()) {
|
|
2086
|
+
endSessionOutput(options, {
|
|
2087
|
+
ok: false,
|
|
2088
|
+
ended: false,
|
|
2089
|
+
mode: 'local',
|
|
2090
|
+
reason: 'multiple_local_sessions_noninteractive',
|
|
2091
|
+
candidates: records.active.map((session) => ({
|
|
2092
|
+
sessionId: session.sessionId,
|
|
2093
|
+
command: `neurcode session end --session-id ${session.sessionId}`,
|
|
2094
|
+
})),
|
|
2095
|
+
malformedRecords: records.malformed,
|
|
2096
|
+
message: 'Multiple active local governance sessions were found; noninteractive selection is disabled.',
|
|
2097
|
+
}, 2);
|
|
1700
2098
|
return;
|
|
1701
2099
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
(
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
const choice = parseInt(answer, 10);
|
|
1718
|
-
if (choice >= 1 && choice <= activeSessions.length) {
|
|
1719
|
-
sessionId = activeSessions[choice - 1].sessionId;
|
|
1720
|
-
}
|
|
1721
|
-
else {
|
|
1722
|
-
(0, messages_1.printError)('Invalid Selection', undefined, ['Please run the command again and select a valid number']);
|
|
1723
|
-
process.exit(1);
|
|
1724
|
-
}
|
|
2100
|
+
console.log(chalk.bold('Multiple active local governance sessions'));
|
|
2101
|
+
records.active.forEach((session, index) => {
|
|
2102
|
+
console.log(` ${index + 1}. ${session.sessionId} ยท ${truncate(session.contract.goal, 72)}`);
|
|
2103
|
+
});
|
|
2104
|
+
const answer = await prompt(`Select local session to end (1-${records.active.length}): `);
|
|
2105
|
+
const selected = Number.parseInt(answer, 10);
|
|
2106
|
+
if (!Number.isInteger(selected) || selected < 1 || selected > records.active.length) {
|
|
2107
|
+
endSessionOutput(options, {
|
|
2108
|
+
ok: false,
|
|
2109
|
+
ended: false,
|
|
2110
|
+
mode: 'local',
|
|
2111
|
+
reason: 'invalid_local_selection',
|
|
2112
|
+
message: 'No local session was ended.',
|
|
2113
|
+
}, 2);
|
|
2114
|
+
return;
|
|
1725
2115
|
}
|
|
2116
|
+
endSessionOutput(options, await finishLocalGovernanceSession(repoRoot, records.active[selected - 1]));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (options.local) {
|
|
2120
|
+
endSessionOutput(options, {
|
|
2121
|
+
ok: true,
|
|
2122
|
+
ended: false,
|
|
2123
|
+
mode: 'local',
|
|
2124
|
+
reason: 'no_active_local_session',
|
|
2125
|
+
malformedRecords: records.malformed,
|
|
2126
|
+
message: 'No active local governance session found.',
|
|
2127
|
+
});
|
|
2128
|
+
return;
|
|
1726
2129
|
}
|
|
1727
2130
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
2131
|
+
let config = null;
|
|
2132
|
+
let client = dependencies.cloudClient;
|
|
2133
|
+
if (!client) {
|
|
2134
|
+
config = (0, config_1.loadConfig)();
|
|
2135
|
+
if (!config.apiKey)
|
|
2136
|
+
config.apiKey = (0, config_1.requireApiKey)();
|
|
2137
|
+
client = new api_client_1.ApiClient(config);
|
|
1735
2138
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
(
|
|
2139
|
+
let sessionId = options.sessionId;
|
|
2140
|
+
if (!sessionId) {
|
|
2141
|
+
const stateSessionId = (0, state_1.getSessionId)() || undefined;
|
|
2142
|
+
if (stateSessionId && (0, governance_runtime_1.loadSession)(repoRoot, stateSessionId)) {
|
|
2143
|
+
const local = (0, governance_runtime_1.loadSession)(repoRoot, stateSessionId);
|
|
2144
|
+
endSessionOutput(options, await finishLocalGovernanceSession(repoRoot, local));
|
|
1742
2145
|
return;
|
|
1743
2146
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
2147
|
+
sessionId = stateSessionId;
|
|
2148
|
+
}
|
|
2149
|
+
if (!sessionId) {
|
|
2150
|
+
const sessions = await client.getSessions(options.projectId || config?.projectId, 20);
|
|
2151
|
+
const active = sessions.filter((session) => session.status === 'active');
|
|
2152
|
+
if (active.length === 0) {
|
|
2153
|
+
endSessionOutput(options, {
|
|
2154
|
+
ok: true,
|
|
2155
|
+
ended: false,
|
|
2156
|
+
mode: 'cloud',
|
|
2157
|
+
reason: 'no_active_cloud_session',
|
|
2158
|
+
message: 'No active local or cloud session found.',
|
|
2159
|
+
});
|
|
1746
2160
|
return;
|
|
1747
2161
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2162
|
+
if (active.length > 1 && !isInteractive()) {
|
|
2163
|
+
endSessionOutput(options, {
|
|
2164
|
+
ok: false,
|
|
2165
|
+
ended: false,
|
|
2166
|
+
mode: 'cloud',
|
|
2167
|
+
reason: 'multiple_cloud_sessions_noninteractive',
|
|
2168
|
+
candidates: active.map((session) => ({
|
|
2169
|
+
sessionId: session.sessionId,
|
|
2170
|
+
command: `neurcode session end --session-id ${session.sessionId}`,
|
|
2171
|
+
})),
|
|
2172
|
+
message: 'Multiple active cloud sessions were found; noninteractive selection is disabled.',
|
|
2173
|
+
}, 2);
|
|
1760
2174
|
return;
|
|
1761
2175
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
try {
|
|
1765
|
-
const currentSessionId = (0, state_1.getSessionId)();
|
|
1766
|
-
if (currentSessionId === sessionId) {
|
|
1767
|
-
const { clearSessionId } = await Promise.resolve().then(() => __importStar(require('../utils/state')));
|
|
1768
|
-
clearSessionId();
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
catch {
|
|
1772
|
-
// Non-critical - continue if state clearing fails
|
|
2176
|
+
if (active.length === 1) {
|
|
2177
|
+
sessionId = active[0].sessionId;
|
|
1773
2178
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
if (roiData && roiData.totalCapitalSaved) {
|
|
1791
|
-
const capitalSaved = typeof roiData.totalCapitalSaved === 'string'
|
|
1792
|
-
? parseFloat(roiData.totalCapitalSaved)
|
|
1793
|
-
: roiData.totalCapitalSaved;
|
|
1794
|
-
const formattedAmount = capitalSaved.toFixed(2);
|
|
1795
|
-
const dashboardUrl = 'https://neurcode.com/dashboard';
|
|
1796
|
-
console.log('');
|
|
1797
|
-
console.log(chalk.cyan('๐'), chalk.bold.white('Current Session ROI:'), chalk.green.bold(`+$${formattedAmount}`));
|
|
1798
|
-
console.log(chalk.dim(` View full report: ${dashboardUrl}`));
|
|
1799
|
-
console.log('');
|
|
1800
|
-
}
|
|
2179
|
+
else {
|
|
2180
|
+
active.forEach((session, index) => {
|
|
2181
|
+
const title = session.title || session.intentDescription || 'Untitled';
|
|
2182
|
+
console.log(` ${index + 1}. ${title} ยท ${session.sessionId}`);
|
|
2183
|
+
});
|
|
2184
|
+
const answer = await prompt(`Select cloud session to end (1-${active.length}): `);
|
|
2185
|
+
const selected = Number.parseInt(answer, 10);
|
|
2186
|
+
if (!Number.isInteger(selected) || selected < 1 || selected > active.length) {
|
|
2187
|
+
endSessionOutput(options, {
|
|
2188
|
+
ok: false,
|
|
2189
|
+
ended: false,
|
|
2190
|
+
mode: 'cloud',
|
|
2191
|
+
reason: 'invalid_cloud_selection',
|
|
2192
|
+
message: 'No cloud session was ended.',
|
|
2193
|
+
}, 2);
|
|
2194
|
+
return;
|
|
1801
2195
|
}
|
|
1802
|
-
|
|
1803
|
-
catch {
|
|
1804
|
-
// Silently fail - ROI summary is a nice-to-have
|
|
2196
|
+
sessionId = active[selected - 1].sessionId;
|
|
1805
2197
|
}
|
|
1806
2198
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
throw error;
|
|
1817
|
-
}
|
|
2199
|
+
if (!sessionId) {
|
|
2200
|
+
endSessionOutput(options, {
|
|
2201
|
+
ok: false,
|
|
2202
|
+
ended: false,
|
|
2203
|
+
mode: 'cloud',
|
|
2204
|
+
reason: 'cloud_session_not_resolved',
|
|
2205
|
+
message: 'No cloud session could be resolved.',
|
|
2206
|
+
}, 2);
|
|
2207
|
+
return;
|
|
1818
2208
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
2209
|
+
const sessionData = await client.getSession(sessionId);
|
|
2210
|
+
const session = sessionData.session;
|
|
2211
|
+
if (session.status === 'completed' || session.status === 'cancelled') {
|
|
2212
|
+
endSessionOutput(options, {
|
|
2213
|
+
ok: true,
|
|
2214
|
+
ended: false,
|
|
2215
|
+
mode: 'cloud',
|
|
2216
|
+
status: session.status,
|
|
2217
|
+
sessionId,
|
|
2218
|
+
message: `Cloud session ${sessionId} is already ${session.status}.`,
|
|
2219
|
+
});
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
if (isInteractive()) {
|
|
2223
|
+
const confirm = await prompt(`End cloud session ${sessionId}? (y/n): `);
|
|
2224
|
+
if (!['y', 'yes'].includes(confirm.toLowerCase())) {
|
|
2225
|
+
endSessionOutput(options, {
|
|
2226
|
+
ok: true,
|
|
2227
|
+
ended: false,
|
|
2228
|
+
mode: 'cloud',
|
|
2229
|
+
reason: 'operator_cancelled',
|
|
2230
|
+
sessionId,
|
|
2231
|
+
message: 'Cloud session was not ended.',
|
|
2232
|
+
});
|
|
2233
|
+
return;
|
|
1824
2234
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
2235
|
+
}
|
|
2236
|
+
await client.endSession(sessionId);
|
|
2237
|
+
try {
|
|
2238
|
+
if ((0, state_1.getSessionId)() === sessionId) {
|
|
2239
|
+
const { clearSessionId } = await Promise.resolve().then(() => __importStar(require('../utils/state')));
|
|
2240
|
+
clearSessionId();
|
|
1827
2241
|
}
|
|
1828
2242
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
2243
|
+
catch {
|
|
2244
|
+
// Legacy local cloud pointer cleanup is best-effort.
|
|
1831
2245
|
}
|
|
1832
|
-
|
|
2246
|
+
endSessionOutput(options, {
|
|
2247
|
+
ok: true,
|
|
2248
|
+
ended: true,
|
|
2249
|
+
mode: 'cloud',
|
|
2250
|
+
status: 'completed',
|
|
2251
|
+
sessionId,
|
|
2252
|
+
message: `Cloud session ${sessionId} ended successfully.`,
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
catch (error) {
|
|
2256
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2257
|
+
const notFound = /not found|404/i.test(message);
|
|
2258
|
+
endSessionOutput(options, {
|
|
2259
|
+
ok: false,
|
|
2260
|
+
ended: false,
|
|
2261
|
+
mode: 'unknown',
|
|
2262
|
+
reason: notFound ? 'session_not_found' : 'session_end_failed',
|
|
2263
|
+
sessionId: options.sessionId || null,
|
|
2264
|
+
error: message,
|
|
2265
|
+
message: notFound
|
|
2266
|
+
? `Session ${options.sessionId || ''} was not found locally or in the cloud.`.trim()
|
|
2267
|
+
: `Failed to end session: ${message}`,
|
|
2268
|
+
}, notFound ? 2 : 1);
|
|
1833
2269
|
}
|
|
1834
2270
|
}
|
|
1835
2271
|
/**
|