pm-orchestrator-runner 1.0.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.
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/cli/cli-interface.d.ts +150 -0
- package/dist/cli/cli-interface.d.ts.map +1 -0
- package/dist/cli/cli-interface.js +606 -0
- package/dist/cli/cli-interface.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +243 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/llm-sentinel.d.ts +15 -0
- package/dist/cli/llm-sentinel.d.ts.map +1 -0
- package/dist/cli/llm-sentinel.js +184 -0
- package/dist/cli/llm-sentinel.js.map +1 -0
- package/dist/config/configuration-manager.d.ts +149 -0
- package/dist/config/configuration-manager.d.ts.map +1 -0
- package/dist/config/configuration-manager.js +241 -0
- package/dist/config/configuration-manager.js.map +1 -0
- package/dist/continuation/continuation-control-manager.d.ts +154 -0
- package/dist/continuation/continuation-control-manager.d.ts.map +1 -0
- package/dist/continuation/continuation-control-manager.js +303 -0
- package/dist/continuation/continuation-control-manager.js.map +1 -0
- package/dist/core/runner-core.d.ts +474 -0
- package/dist/core/runner-core.d.ts.map +1 -0
- package/dist/core/runner-core.js +1311 -0
- package/dist/core/runner-core.js.map +1 -0
- package/dist/errors/error-codes.d.ts +105 -0
- package/dist/errors/error-codes.d.ts.map +1 -0
- package/dist/errors/error-codes.js +198 -0
- package/dist/errors/error-codes.js.map +1 -0
- package/dist/errors/runner-error.d.ts +14 -0
- package/dist/errors/runner-error.d.ts.map +1 -0
- package/dist/errors/runner-error.js +33 -0
- package/dist/errors/runner-error.js.map +1 -0
- package/dist/evidence/evidence-manager.d.ts +112 -0
- package/dist/evidence/evidence-manager.d.ts.map +1 -0
- package/dist/evidence/evidence-manager.js +337 -0
- package/dist/evidence/evidence-manager.js.map +1 -0
- package/dist/executor/claude-code-executor.d.ts +136 -0
- package/dist/executor/claude-code-executor.d.ts.map +1 -0
- package/dist/executor/claude-code-executor.js +643 -0
- package/dist/executor/claude-code-executor.js.map +1 -0
- package/dist/executor/deterministic-executor.d.ts +40 -0
- package/dist/executor/deterministic-executor.d.ts.map +1 -0
- package/dist/executor/deterministic-executor.js +269 -0
- package/dist/executor/deterministic-executor.js.map +1 -0
- package/dist/lifecycle/lifecycle-controller.d.ts +270 -0
- package/dist/lifecycle/lifecycle-controller.d.ts.map +1 -0
- package/dist/lifecycle/lifecycle-controller.js +596 -0
- package/dist/lifecycle/lifecycle-controller.js.map +1 -0
- package/dist/limits/resource-limit-manager.d.ts +200 -0
- package/dist/limits/resource-limit-manager.d.ts.map +1 -0
- package/dist/limits/resource-limit-manager.js +376 -0
- package/dist/limits/resource-limit-manager.js.map +1 -0
- package/dist/locks/lock-manager.d.ts +116 -0
- package/dist/locks/lock-manager.d.ts.map +1 -0
- package/dist/locks/lock-manager.js +306 -0
- package/dist/locks/lock-manager.js.map +1 -0
- package/dist/logging/index.d.ts +8 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +22 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/sensitive-data-masker.d.ts +90 -0
- package/dist/logging/sensitive-data-masker.d.ts.map +1 -0
- package/dist/logging/sensitive-data-masker.js +228 -0
- package/dist/logging/sensitive-data-masker.js.map +1 -0
- package/dist/logging/task-log-manager.d.ts +215 -0
- package/dist/logging/task-log-manager.d.ts.map +1 -0
- package/dist/logging/task-log-manager.js +743 -0
- package/dist/logging/task-log-manager.js.map +1 -0
- package/dist/mediation/fail-closed-runner.d.ts +131 -0
- package/dist/mediation/fail-closed-runner.d.ts.map +1 -0
- package/dist/mediation/fail-closed-runner.js +245 -0
- package/dist/mediation/fail-closed-runner.js.map +1 -0
- package/dist/mediation/llm-client-with-evidence.d.ts +123 -0
- package/dist/mediation/llm-client-with-evidence.d.ts.map +1 -0
- package/dist/mediation/llm-client-with-evidence.js +245 -0
- package/dist/mediation/llm-client-with-evidence.js.map +1 -0
- package/dist/mediation/llm-client.d.ts +102 -0
- package/dist/mediation/llm-client.d.ts.map +1 -0
- package/dist/mediation/llm-client.js +206 -0
- package/dist/mediation/llm-client.js.map +1 -0
- package/dist/mediation/llm-evidence-manager.d.ts +108 -0
- package/dist/mediation/llm-evidence-manager.d.ts.map +1 -0
- package/dist/mediation/llm-evidence-manager.js +230 -0
- package/dist/mediation/llm-evidence-manager.js.map +1 -0
- package/dist/mediation/llm-mediation-layer.d.ts +175 -0
- package/dist/mediation/llm-mediation-layer.d.ts.map +1 -0
- package/dist/mediation/llm-mediation-layer.js +315 -0
- package/dist/mediation/llm-mediation-layer.js.map +1 -0
- package/dist/mediation/llm-sentinel.d.ts +107 -0
- package/dist/mediation/llm-sentinel.d.ts.map +1 -0
- package/dist/mediation/llm-sentinel.js +187 -0
- package/dist/mediation/llm-sentinel.js.map +1 -0
- package/dist/mediation/real-llm-mediation-layer.d.ts +104 -0
- package/dist/mediation/real-llm-mediation-layer.d.ts.map +1 -0
- package/dist/mediation/real-llm-mediation-layer.js +322 -0
- package/dist/mediation/real-llm-mediation-layer.js.map +1 -0
- package/dist/mediation/verdict-reporter.d.ts +61 -0
- package/dist/mediation/verdict-reporter.d.ts.map +1 -0
- package/dist/mediation/verdict-reporter.js +178 -0
- package/dist/mediation/verdict-reporter.js.map +1 -0
- package/dist/models/enums.d.ts +133 -0
- package/dist/models/enums.d.ts.map +1 -0
- package/dist/models/enums.js +201 -0
- package/dist/models/enums.js.map +1 -0
- package/dist/models/evidence.d.ts +60 -0
- package/dist/models/evidence.d.ts.map +1 -0
- package/dist/models/evidence.js +135 -0
- package/dist/models/evidence.js.map +1 -0
- package/dist/models/execution-result.d.ts +89 -0
- package/dist/models/execution-result.d.ts.map +1 -0
- package/dist/models/execution-result.js +197 -0
- package/dist/models/execution-result.js.map +1 -0
- package/dist/models/file-lock.d.ts +62 -0
- package/dist/models/file-lock.d.ts.map +1 -0
- package/dist/models/file-lock.js +133 -0
- package/dist/models/file-lock.js.map +1 -0
- package/dist/models/index.d.ts +12 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +91 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/repl/index.d.ts +7 -0
- package/dist/models/repl/index.d.ts.map +1 -0
- package/dist/models/repl/index.js +32 -0
- package/dist/models/repl/index.js.map +1 -0
- package/dist/models/repl/model-registry.d.ts +73 -0
- package/dist/models/repl/model-registry.d.ts.map +1 -0
- package/dist/models/repl/model-registry.js +116 -0
- package/dist/models/repl/model-registry.js.map +1 -0
- package/dist/models/repl/repl-state.d.ts +86 -0
- package/dist/models/repl/repl-state.d.ts.map +1 -0
- package/dist/models/repl/repl-state.js +152 -0
- package/dist/models/repl/repl-state.js.map +1 -0
- package/dist/models/repl/task-log.d.ts +247 -0
- package/dist/models/repl/task-log.d.ts.map +1 -0
- package/dist/models/repl/task-log.js +178 -0
- package/dist/models/repl/task-log.js.map +1 -0
- package/dist/models/session.d.ts +71 -0
- package/dist/models/session.d.ts.map +1 -0
- package/dist/models/session.js +140 -0
- package/dist/models/session.js.map +1 -0
- package/dist/models/supporting.d.ts +97 -0
- package/dist/models/supporting.d.ts.map +1 -0
- package/dist/models/supporting.js +208 -0
- package/dist/models/supporting.js.map +1 -0
- package/dist/models/task.d.ts +77 -0
- package/dist/models/task.d.ts.map +1 -0
- package/dist/models/task.js +170 -0
- package/dist/models/task.js.map +1 -0
- package/dist/output/output-control-manager.d.ts +217 -0
- package/dist/output/output-control-manager.d.ts.map +1 -0
- package/dist/output/output-control-manager.js +378 -0
- package/dist/output/output-control-manager.js.map +1 -0
- package/dist/pool/agent-pool.d.ts +284 -0
- package/dist/pool/agent-pool.d.ts.map +1 -0
- package/dist/pool/agent-pool.js +451 -0
- package/dist/pool/agent-pool.js.map +1 -0
- package/dist/repl/commands/index.d.ts +12 -0
- package/dist/repl/commands/index.d.ts.map +1 -0
- package/dist/repl/commands/index.js +26 -0
- package/dist/repl/commands/index.js.map +1 -0
- package/dist/repl/commands/init.d.ts +31 -0
- package/dist/repl/commands/init.d.ts.map +1 -0
- package/dist/repl/commands/init.js +234 -0
- package/dist/repl/commands/init.js.map +1 -0
- package/dist/repl/commands/keys.d.ts +63 -0
- package/dist/repl/commands/keys.d.ts.map +1 -0
- package/dist/repl/commands/keys.js +114 -0
- package/dist/repl/commands/keys.js.map +1 -0
- package/dist/repl/commands/logs.d.ts +91 -0
- package/dist/repl/commands/logs.d.ts.map +1 -0
- package/dist/repl/commands/logs.js +200 -0
- package/dist/repl/commands/logs.js.map +1 -0
- package/dist/repl/commands/model.d.ts +85 -0
- package/dist/repl/commands/model.d.ts.map +1 -0
- package/dist/repl/commands/model.js +225 -0
- package/dist/repl/commands/model.js.map +1 -0
- package/dist/repl/commands/models.d.ts +50 -0
- package/dist/repl/commands/models.d.ts.map +1 -0
- package/dist/repl/commands/models.js +180 -0
- package/dist/repl/commands/models.js.map +1 -0
- package/dist/repl/commands/provider.d.ts +79 -0
- package/dist/repl/commands/provider.d.ts.map +1 -0
- package/dist/repl/commands/provider.js +291 -0
- package/dist/repl/commands/provider.js.map +1 -0
- package/dist/repl/commands/session.d.ts +50 -0
- package/dist/repl/commands/session.d.ts.map +1 -0
- package/dist/repl/commands/session.js +152 -0
- package/dist/repl/commands/session.js.map +1 -0
- package/dist/repl/commands/status.d.ts +55 -0
- package/dist/repl/commands/status.d.ts.map +1 -0
- package/dist/repl/commands/status.js +182 -0
- package/dist/repl/commands/status.js.map +1 -0
- package/dist/repl/index.d.ts +6 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +25 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/repl-interface.d.ts +371 -0
- package/dist/repl/repl-interface.d.ts.map +1 -0
- package/dist/repl/repl-interface.js +1214 -0
- package/dist/repl/repl-interface.js.map +1 -0
- package/dist/session/session-manager.d.ts +85 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +217 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/supervisor/executor-supervisor.d.ts +90 -0
- package/dist/supervisor/executor-supervisor.d.ts.map +1 -0
- package/dist/supervisor/executor-supervisor.js +223 -0
- package/dist/supervisor/executor-supervisor.js.map +1 -0
- package/dist/supervisor/index.d.ts +5 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +9 -0
- package/dist/supervisor/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* REPL Interface for PM Orchestrator Runner
|
|
4
|
+
* Provides an interactive CLI experience with slash commands
|
|
5
|
+
*
|
|
6
|
+
* Supports two execution modes (per spec 10_REPL_UX.md):
|
|
7
|
+
* - Interactive mode: TTY connected, readline with prompt
|
|
8
|
+
* - Non-interactive mode: stdin script / heredoc / pipe
|
|
9
|
+
*
|
|
10
|
+
* Non-interactive mode guarantees:
|
|
11
|
+
* - Sequential Processing: Each command completes before next starts
|
|
12
|
+
* - Output Flush: All stdout is flushed before exit
|
|
13
|
+
* - Deterministic Exit Code: 0=COMPLETE, 1=ERROR, 2=INCOMPLETE
|
|
14
|
+
*
|
|
15
|
+
* Project Mode (per spec 10_REPL_UX.md, Property 32, 33):
|
|
16
|
+
* - temp: Use temporary directory (default, cleaned up on exit)
|
|
17
|
+
* - fixed: Use specified directory (persists after exit)
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.REPLInterface = exports.EXIT_CODES = void 0;
|
|
54
|
+
const readline = __importStar(require("readline"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const os = __importStar(require("os"));
|
|
58
|
+
const events_1 = require("events");
|
|
59
|
+
const enums_1 = require("../models/enums");
|
|
60
|
+
const executor_supervisor_1 = require("../supervisor/executor-supervisor");
|
|
61
|
+
const init_1 = require("./commands/init");
|
|
62
|
+
const model_1 = require("./commands/model");
|
|
63
|
+
const session_1 = require("./commands/session");
|
|
64
|
+
const status_1 = require("./commands/status");
|
|
65
|
+
const provider_1 = require("./commands/provider");
|
|
66
|
+
const models_1 = require("./commands/models");
|
|
67
|
+
const keys_1 = require("./commands/keys");
|
|
68
|
+
const logs_1 = require("./commands/logs");
|
|
69
|
+
/**
|
|
70
|
+
* Exit codes for non-interactive mode - per spec 10_REPL_UX.md
|
|
71
|
+
* These are deterministic based on session state
|
|
72
|
+
*/
|
|
73
|
+
exports.EXIT_CODES = {
|
|
74
|
+
COMPLETE: 0, // All tasks completed successfully
|
|
75
|
+
ERROR: 1, // Error occurred during execution
|
|
76
|
+
INCOMPLETE: 2, // Session ended with incomplete tasks
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Commands allowed in init-only mode
|
|
80
|
+
* When .claude is missing, only these commands are available
|
|
81
|
+
*/
|
|
82
|
+
const INIT_ONLY_ALLOWED_COMMANDS = ['help', 'init', 'exit'];
|
|
83
|
+
/**
|
|
84
|
+
* All known commands (for unknown command detection)
|
|
85
|
+
* Updated per spec 10_REPL_UX.md to include new commands
|
|
86
|
+
*/
|
|
87
|
+
const KNOWN_COMMANDS = [
|
|
88
|
+
'help', 'init', 'model', 'start', 'continue',
|
|
89
|
+
'status', 'tasks', 'approve', 'exit',
|
|
90
|
+
// New commands per spec 10_REPL_UX.md
|
|
91
|
+
'provider', 'models', 'keys', 'logs'
|
|
92
|
+
];
|
|
93
|
+
/**
|
|
94
|
+
* Commands with slash prefix for tab completion
|
|
95
|
+
* Per spec 10_REPL_UX.md: Tab completion for slash commands
|
|
96
|
+
*/
|
|
97
|
+
const SLASH_COMMANDS = KNOWN_COMMANDS.map(cmd => '/' + cmd);
|
|
98
|
+
/**
|
|
99
|
+
* REPL Interface class
|
|
100
|
+
*/
|
|
101
|
+
class REPLInterface extends events_1.EventEmitter {
|
|
102
|
+
config;
|
|
103
|
+
rl = null;
|
|
104
|
+
session;
|
|
105
|
+
running = false;
|
|
106
|
+
initOnlyMode = false;
|
|
107
|
+
// Sequential input processing (prevents race conditions with piped input)
|
|
108
|
+
inputQueue = [];
|
|
109
|
+
isProcessingInput = false;
|
|
110
|
+
// Non-interactive mode support (per spec 10_REPL_UX.md)
|
|
111
|
+
executionMode;
|
|
112
|
+
exitCode = exports.EXIT_CODES.COMPLETE;
|
|
113
|
+
hasError = false;
|
|
114
|
+
hasIncompleteTasks = false;
|
|
115
|
+
// Session completion tracking (prevents double completion)
|
|
116
|
+
sessionCompleted = false;
|
|
117
|
+
// Project mode support (per spec 10_REPL_UX.md, Property 32, 33)
|
|
118
|
+
projectMode;
|
|
119
|
+
verificationRoot;
|
|
120
|
+
tempVerificationRoot = null;
|
|
121
|
+
// Command handlers
|
|
122
|
+
initCommand;
|
|
123
|
+
modelCommand;
|
|
124
|
+
sessionCommands;
|
|
125
|
+
statusCommands;
|
|
126
|
+
// New command handlers per spec 10_REPL_UX.md
|
|
127
|
+
providerCommand;
|
|
128
|
+
modelsCommand;
|
|
129
|
+
keysCommand;
|
|
130
|
+
logsCommand;
|
|
131
|
+
constructor(config = {}) {
|
|
132
|
+
super();
|
|
133
|
+
// Validate fixed mode configuration (Property 32)
|
|
134
|
+
if (config.projectMode === 'fixed') {
|
|
135
|
+
if (!config.projectRoot) {
|
|
136
|
+
throw new Error('project-root is required when project-mode is fixed');
|
|
137
|
+
}
|
|
138
|
+
if (!fs.existsSync(config.projectRoot)) {
|
|
139
|
+
throw new Error('project-root does not exist: ' + config.projectRoot);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Determine project mode
|
|
143
|
+
this.projectMode = config.projectMode || 'temp';
|
|
144
|
+
// Set verification root based on mode
|
|
145
|
+
if (this.projectMode === 'fixed') {
|
|
146
|
+
this.verificationRoot = config.projectRoot;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Temp mode: create temporary directory immediately (synchronous)
|
|
150
|
+
// This ensures getVerificationRoot() always returns a valid path
|
|
151
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-runner-'));
|
|
152
|
+
this.tempVerificationRoot = tempDir;
|
|
153
|
+
this.verificationRoot = tempDir;
|
|
154
|
+
// Create minimal .claude structure to avoid init-only mode
|
|
155
|
+
const claudeDir = path.join(tempDir, '.claude');
|
|
156
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
157
|
+
const claudeMdContent = '# Temporary Project\n\nCreated by pm-orchestrator-runner in temp mode.\n';
|
|
158
|
+
fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), claudeMdContent, 'utf-8');
|
|
159
|
+
const settingsContent = JSON.stringify({
|
|
160
|
+
project: { name: 'temp-project', version: '1.0.0' },
|
|
161
|
+
pm: { autoStart: false },
|
|
162
|
+
}, null, 2);
|
|
163
|
+
fs.writeFileSync(path.join(claudeDir, 'settings.json'), settingsContent, 'utf-8');
|
|
164
|
+
}
|
|
165
|
+
// CRITICAL: In temp mode, use verificationRoot for projectPath and evidenceDir
|
|
166
|
+
// This ensures files are created in the temp directory, not process.cwd()
|
|
167
|
+
// In fixed mode, use projectRoot (from --project-root) if provided
|
|
168
|
+
const resolvedProjectPath = this.projectMode === 'temp'
|
|
169
|
+
? this.verificationRoot
|
|
170
|
+
: (config.projectRoot || config.projectPath || process.cwd());
|
|
171
|
+
this.config = {
|
|
172
|
+
projectPath: resolvedProjectPath,
|
|
173
|
+
evidenceDir: config.evidenceDir || path.join(resolvedProjectPath, '.claude', 'evidence'),
|
|
174
|
+
prompt: config.prompt || 'pm> ',
|
|
175
|
+
authMode: config.authMode || 'claude-code',
|
|
176
|
+
timeout: config.timeout || 120000,
|
|
177
|
+
forceNonInteractive: config.forceNonInteractive,
|
|
178
|
+
projectMode: config.projectMode,
|
|
179
|
+
projectRoot: config.projectRoot,
|
|
180
|
+
printProjectPath: config.printProjectPath,
|
|
181
|
+
};
|
|
182
|
+
// Detect execution mode (per spec 10_REPL_UX.md)
|
|
183
|
+
// Non-interactive when: stdin is not TTY, or forced via config/env
|
|
184
|
+
this.executionMode = this.detectExecutionMode();
|
|
185
|
+
this.session = {
|
|
186
|
+
sessionId: null,
|
|
187
|
+
projectPath: this.config.projectPath,
|
|
188
|
+
runner: null,
|
|
189
|
+
supervisor: null,
|
|
190
|
+
status: 'idle',
|
|
191
|
+
};
|
|
192
|
+
// Initialize command handlers
|
|
193
|
+
this.initCommand = new init_1.InitCommand();
|
|
194
|
+
this.modelCommand = new model_1.ModelCommand();
|
|
195
|
+
this.sessionCommands = new session_1.SessionCommands(this.session, this.config);
|
|
196
|
+
this.statusCommands = new status_1.StatusCommands(this.session);
|
|
197
|
+
// Initialize new command handlers
|
|
198
|
+
this.providerCommand = new provider_1.ProviderCommand();
|
|
199
|
+
this.modelsCommand = new models_1.ModelsCommand();
|
|
200
|
+
this.keysCommand = new keys_1.KeysCommand();
|
|
201
|
+
this.logsCommand = new logs_1.LogsCommand();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get project mode - per spec 10_REPL_UX.md
|
|
205
|
+
* @returns Current project mode ('temp' or 'fixed')
|
|
206
|
+
*/
|
|
207
|
+
getProjectMode() {
|
|
208
|
+
return this.projectMode;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get verification root - per spec Property 32, 33
|
|
212
|
+
* @returns Absolute path to verification root directory
|
|
213
|
+
*/
|
|
214
|
+
getVerificationRoot() {
|
|
215
|
+
return this.verificationRoot;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Initialize for use - called by start() or manually for testing
|
|
219
|
+
* Handles project path setup and PROJECT_PATH output
|
|
220
|
+
*/
|
|
221
|
+
async initialize() {
|
|
222
|
+
// Initialize temp project root if in temp mode
|
|
223
|
+
if (this.projectMode === 'temp' && !this.verificationRoot) {
|
|
224
|
+
await this.initializeTempProjectRoot();
|
|
225
|
+
}
|
|
226
|
+
// Output PROJECT_PATH if requested (per spec 10_REPL_UX.md)
|
|
227
|
+
if (this.config.printProjectPath) {
|
|
228
|
+
console.log('PROJECT_PATH=' + this.verificationRoot);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Initialize temporary project root - per spec Property 32
|
|
233
|
+
* Creates a temporary directory for verification_root in temp mode
|
|
234
|
+
* Also creates minimal .claude structure to avoid init-only mode
|
|
235
|
+
*/
|
|
236
|
+
async initializeTempProjectRoot() {
|
|
237
|
+
if (this.projectMode !== 'temp') {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Create temp directory
|
|
241
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pm-runner-'));
|
|
242
|
+
this.tempVerificationRoot = tempDir;
|
|
243
|
+
this.verificationRoot = tempDir;
|
|
244
|
+
// Create minimal .claude structure for temp mode
|
|
245
|
+
// This prevents init-only mode and allows /start to work immediately
|
|
246
|
+
const claudeDir = path.join(tempDir, '.claude');
|
|
247
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
248
|
+
// Create minimal CLAUDE.md
|
|
249
|
+
const claudeMdContent = '# Temporary Project\n\nCreated by pm-orchestrator-runner in temp mode.\n';
|
|
250
|
+
fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), claudeMdContent, 'utf-8');
|
|
251
|
+
// Create minimal settings.json
|
|
252
|
+
const settingsContent = JSON.stringify({
|
|
253
|
+
project: { name: 'temp-project', version: '1.0.0' },
|
|
254
|
+
pm: { autoStart: false },
|
|
255
|
+
}, null, 2);
|
|
256
|
+
fs.writeFileSync(path.join(claudeDir, 'settings.json'), settingsContent, 'utf-8');
|
|
257
|
+
// Update config.projectPath to point to temp directory
|
|
258
|
+
// This ensures validateProjectStructure checks the right path
|
|
259
|
+
this.config.projectPath = tempDir;
|
|
260
|
+
this.session.projectPath = tempDir;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Verify files and return verification records - per spec Property 33
|
|
264
|
+
* @param absolutePaths - Array of absolute file paths to verify
|
|
265
|
+
* @returns Array of VerifiedFile records with relative paths
|
|
266
|
+
*/
|
|
267
|
+
verifyFiles(absolutePaths) {
|
|
268
|
+
const now = new Date().toISOString();
|
|
269
|
+
return absolutePaths.map(absolutePath => {
|
|
270
|
+
// Convert to relative path from verification_root
|
|
271
|
+
const relativePath = path.relative(this.verificationRoot, absolutePath);
|
|
272
|
+
return {
|
|
273
|
+
path: relativePath,
|
|
274
|
+
exists: fs.existsSync(absolutePath),
|
|
275
|
+
detected_at: now,
|
|
276
|
+
detection_method: 'diff',
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Create a TaskLog with verification info - per spec Property 33
|
|
282
|
+
* @param taskId - Task identifier
|
|
283
|
+
* @param description - Task description
|
|
284
|
+
* @returns TaskLog with verification_root populated
|
|
285
|
+
*/
|
|
286
|
+
createTaskLog(taskId, description) {
|
|
287
|
+
return {
|
|
288
|
+
task_id: taskId,
|
|
289
|
+
description,
|
|
290
|
+
verification_root: this.verificationRoot,
|
|
291
|
+
created_at: new Date().toISOString(),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Cleanup resources - handles temp directory cleanup
|
|
296
|
+
* Per spec Property 32: temp mode directories may be cleaned up
|
|
297
|
+
*/
|
|
298
|
+
async cleanup() {
|
|
299
|
+
if (this.session.supervisor) {
|
|
300
|
+
this.session.supervisor.stop();
|
|
301
|
+
}
|
|
302
|
+
// Note: In temp mode, we don't forcefully delete the temp directory
|
|
303
|
+
// The OS will clean it up eventually, and leaving it allows for debugging
|
|
304
|
+
// In fixed mode, the directory persists as expected
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Start the REPL
|
|
308
|
+
* Per spec 10_REPL_UX.md L45: validate project structure on startup
|
|
309
|
+
*
|
|
310
|
+
* If .claude is missing, enter init-only mode instead of throwing.
|
|
311
|
+
* In init-only mode, only /help, /init, /exit are available.
|
|
312
|
+
*/
|
|
313
|
+
async start() {
|
|
314
|
+
// Initialize project root
|
|
315
|
+
await this.initialize();
|
|
316
|
+
// Validate project structure per spec 10_REPL_UX.md L45
|
|
317
|
+
const validation = await this.validateProjectStructure();
|
|
318
|
+
if (!validation.valid) {
|
|
319
|
+
// Enter init-only mode instead of failing
|
|
320
|
+
this.initOnlyMode = true;
|
|
321
|
+
console.log('');
|
|
322
|
+
console.log('WARNING: Project not initialized');
|
|
323
|
+
for (const error of validation.errors) {
|
|
324
|
+
console.log(' - ' + error);
|
|
325
|
+
}
|
|
326
|
+
console.log('');
|
|
327
|
+
console.log('Entering init-only mode.');
|
|
328
|
+
console.log('Only /help, /init, and /exit are available.');
|
|
329
|
+
console.log('Run /init to initialize the project structure.');
|
|
330
|
+
console.log('');
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
this.initOnlyMode = false;
|
|
334
|
+
}
|
|
335
|
+
this.running = true;
|
|
336
|
+
this.rl = readline.createInterface({
|
|
337
|
+
input: process.stdin,
|
|
338
|
+
output: process.stdout,
|
|
339
|
+
prompt: this.config.prompt,
|
|
340
|
+
completer: this.completer.bind(this),
|
|
341
|
+
});
|
|
342
|
+
// Print welcome message (or init-only mode reminder)
|
|
343
|
+
if (!this.initOnlyMode) {
|
|
344
|
+
this.printWelcome();
|
|
345
|
+
}
|
|
346
|
+
this.rl.prompt();
|
|
347
|
+
this.rl.on('line', (line) => {
|
|
348
|
+
// Queue-based sequential processing to prevent race conditions
|
|
349
|
+
// This ensures /start completes before subsequent commands are processed
|
|
350
|
+
this.enqueueInput(line);
|
|
351
|
+
});
|
|
352
|
+
// Return a promise that resolves when REPL exits
|
|
353
|
+
// CRITICAL: Single 'close' handler to avoid race conditions
|
|
354
|
+
// Previous bug: two handlers - one waiting for queue drain, one resolving immediately
|
|
355
|
+
return new Promise((resolve) => {
|
|
356
|
+
this.rl.on('close', async () => {
|
|
357
|
+
// In non-interactive mode, wait for all queued input to be processed
|
|
358
|
+
// This ensures all commands complete before EOF handling
|
|
359
|
+
if (this.executionMode === 'non_interactive') {
|
|
360
|
+
// Wait for input queue to drain, OR exit immediately if running is false
|
|
361
|
+
// running = false means /exit was called, so remaining queue items should be ignored
|
|
362
|
+
while ((this.inputQueue.length > 0 || this.isProcessingInput) && this.running) {
|
|
363
|
+
await new Promise(r => setTimeout(r, 10));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.running = false;
|
|
367
|
+
// Ensure all output is flushed before cleanup
|
|
368
|
+
await this.flushStdout();
|
|
369
|
+
await this.cleanup();
|
|
370
|
+
// In non-interactive mode, set process exit code
|
|
371
|
+
if (this.executionMode === 'non_interactive') {
|
|
372
|
+
this.updateExitCode();
|
|
373
|
+
process.exitCode = this.exitCode;
|
|
374
|
+
}
|
|
375
|
+
resolve();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Process input line
|
|
381
|
+
*/
|
|
382
|
+
async processInput(input) {
|
|
383
|
+
if (input.startsWith('/')) {
|
|
384
|
+
await this.processCommand(input);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
await this.processNaturalLanguage(input);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Enqueue input for sequential processing
|
|
392
|
+
* This prevents race conditions when piped input arrives faster than processing
|
|
393
|
+
*/
|
|
394
|
+
enqueueInput(line) {
|
|
395
|
+
const trimmed = line.trim();
|
|
396
|
+
if (!trimmed) {
|
|
397
|
+
// Empty line - just show prompt if not processing
|
|
398
|
+
if (!this.isProcessingInput && this.running) {
|
|
399
|
+
this.rl?.prompt();
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.inputQueue.push(trimmed);
|
|
404
|
+
// Start processing if not already processing
|
|
405
|
+
this.processQueue();
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Process queued inputs sequentially
|
|
409
|
+
* Ensures each input completes before the next one starts
|
|
410
|
+
* Per spec 10_REPL_UX.md: Sequential Processing Guarantee
|
|
411
|
+
*/
|
|
412
|
+
async processQueue() {
|
|
413
|
+
// Already processing - let the current processor handle the queue
|
|
414
|
+
if (this.isProcessingInput) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.isProcessingInput = true;
|
|
418
|
+
try {
|
|
419
|
+
while (this.inputQueue.length > 0 && this.running) {
|
|
420
|
+
const input = this.inputQueue.shift();
|
|
421
|
+
try {
|
|
422
|
+
await this.processInput(input);
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
this.printError(err);
|
|
426
|
+
}
|
|
427
|
+
// In non-interactive mode, flush output after each command
|
|
428
|
+
// This ensures output is visible before processing next command
|
|
429
|
+
if (this.executionMode === 'non_interactive') {
|
|
430
|
+
await this.flushStdout();
|
|
431
|
+
}
|
|
432
|
+
// Show prompt after each input if still running (interactive mode only)
|
|
433
|
+
if (this.running && this.inputQueue.length === 0) {
|
|
434
|
+
if (this.executionMode === 'interactive') {
|
|
435
|
+
this.rl?.prompt();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
this.isProcessingInput = false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Process slash command
|
|
446
|
+
* Per spec 10_REPL_UX.md L66: All commands must return a status (fail-closed)
|
|
447
|
+
*
|
|
448
|
+
* In init-only mode, only /help, /init, /exit are allowed.
|
|
449
|
+
* Other commands return ERROR with instruction to run /init first.
|
|
450
|
+
*/
|
|
451
|
+
async processCommand(input) {
|
|
452
|
+
const parts = input.slice(1).split(/\s+/);
|
|
453
|
+
const command = parts[0].toLowerCase();
|
|
454
|
+
const args = parts.slice(1);
|
|
455
|
+
// First check if command is known (E201 for unknown takes precedence)
|
|
456
|
+
if (!KNOWN_COMMANDS.includes(command)) {
|
|
457
|
+
// Per spec 10_REPL_UX.md L66: Unknown commands must return ERROR
|
|
458
|
+
this.print('Unknown command: /' + command);
|
|
459
|
+
this.print('Type /help for available commands.');
|
|
460
|
+
return {
|
|
461
|
+
success: false,
|
|
462
|
+
error: {
|
|
463
|
+
code: 'E201',
|
|
464
|
+
message: 'Unknown command: /' + command + '. Type /help for available commands.',
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
// Check if in init-only mode
|
|
469
|
+
const isInitOnly = await this.isInitOnlyMode();
|
|
470
|
+
// In init-only mode, block commands that require .claude
|
|
471
|
+
if (isInitOnly && !INIT_ONLY_ALLOWED_COMMANDS.includes(command)) {
|
|
472
|
+
this.print('ERROR: Cannot run /' + command + ' - project not initialized.');
|
|
473
|
+
this.print('Run /init first to create .claude directory.');
|
|
474
|
+
return {
|
|
475
|
+
success: false,
|
|
476
|
+
error: {
|
|
477
|
+
code: 'E301',
|
|
478
|
+
message: 'Cannot run /' + command + ' in init-only mode. Run /init first to initialize the project.',
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
switch (command) {
|
|
483
|
+
case 'help':
|
|
484
|
+
this.printHelp();
|
|
485
|
+
return { success: true };
|
|
486
|
+
case 'init':
|
|
487
|
+
return await this.handleInit(args);
|
|
488
|
+
case 'model':
|
|
489
|
+
return await this.handleModel(args);
|
|
490
|
+
case 'tasks':
|
|
491
|
+
await this.handleTasks();
|
|
492
|
+
return { success: true };
|
|
493
|
+
case 'status':
|
|
494
|
+
await this.handleStatus();
|
|
495
|
+
return { success: true };
|
|
496
|
+
case 'start':
|
|
497
|
+
return await this.handleStart(args);
|
|
498
|
+
case 'continue':
|
|
499
|
+
return await this.handleContinue(args);
|
|
500
|
+
case 'approve':
|
|
501
|
+
return await this.handleApprove();
|
|
502
|
+
case 'exit':
|
|
503
|
+
await this.handleExit();
|
|
504
|
+
return { success: true };
|
|
505
|
+
// New commands per spec 10_REPL_UX.md
|
|
506
|
+
case 'provider':
|
|
507
|
+
return await this.handleProvider(args);
|
|
508
|
+
case 'models':
|
|
509
|
+
return await this.handleModels(args);
|
|
510
|
+
case 'keys':
|
|
511
|
+
return await this.handleKeys(args);
|
|
512
|
+
case 'logs':
|
|
513
|
+
return await this.handleLogs(args);
|
|
514
|
+
default:
|
|
515
|
+
// This should never be reached since unknown commands are handled above
|
|
516
|
+
// If we reach here, KNOWN_COMMANDS list is inconsistent with switch cases
|
|
517
|
+
throw new Error('Internal error: command "' + command + '" is in KNOWN_COMMANDS but not handled in switch');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Process natural language input
|
|
522
|
+
* Per spec 10_REPL_UX.md L117-118: Model selection is REPL-local
|
|
523
|
+
* Model is read from .claude/repl.json and passed to executor via runner
|
|
524
|
+
*/
|
|
525
|
+
async processNaturalLanguage(input) {
|
|
526
|
+
console.log(`[DEBUG processNaturalLanguage] start, input="${input}"`);
|
|
527
|
+
if (this.session.status !== 'running') {
|
|
528
|
+
this.print('No active session. Use /start to begin, or /init to set up a new project.');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!this.session.runner) {
|
|
532
|
+
this.print('Runner not initialized. Use /start first.');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
this.print('Processing task: ' + input);
|
|
536
|
+
this.print('');
|
|
537
|
+
try {
|
|
538
|
+
// Per spec 10_REPL_UX.md L117-118: Get selected model from REPL config
|
|
539
|
+
// Model is REPL-local, stored in .claude/repl.json
|
|
540
|
+
let selectedModel;
|
|
541
|
+
const modelResult = await this.modelCommand.getModel(this.session.projectPath);
|
|
542
|
+
if (modelResult.success && modelResult.model && modelResult.model !== 'UNSET') {
|
|
543
|
+
selectedModel = modelResult.model;
|
|
544
|
+
}
|
|
545
|
+
console.log(`[DEBUG processNaturalLanguage] calling runner.execute...`);
|
|
546
|
+
// Create task from natural language input
|
|
547
|
+
// CRITICAL: naturalLanguageTask must be set to trigger Claude Code execution
|
|
548
|
+
// Without this, the task would only use fallback file creation patterns
|
|
549
|
+
const result = await this.session.runner.execute({
|
|
550
|
+
tasks: [{
|
|
551
|
+
id: 'task-' + Date.now(),
|
|
552
|
+
description: input,
|
|
553
|
+
naturalLanguageTask: input,
|
|
554
|
+
}],
|
|
555
|
+
selectedModel,
|
|
556
|
+
});
|
|
557
|
+
console.log(`[DEBUG processNaturalLanguage] runner.execute returned, status=${result.overall_status}`);
|
|
558
|
+
this.printExecutionResult(result);
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
console.log(`[DEBUG processNaturalLanguage] error: ${err.message}`);
|
|
562
|
+
this.printError(err);
|
|
563
|
+
}
|
|
564
|
+
console.log(`[DEBUG processNaturalLanguage] done`);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Print welcome message
|
|
568
|
+
*/
|
|
569
|
+
printWelcome() {
|
|
570
|
+
this.print('');
|
|
571
|
+
this.print('PM Orchestrator Runner - Interactive Mode');
|
|
572
|
+
this.print('=========================================');
|
|
573
|
+
this.print('Project: ' + this.session.projectPath);
|
|
574
|
+
this.print('Auth Mode: ' + this.config.authMode + ' (L1 uses Claude Code CLI)');
|
|
575
|
+
this.print('Project Mode: ' + this.projectMode);
|
|
576
|
+
if (this.projectMode === 'fixed') {
|
|
577
|
+
this.print('Verification Root: ' + this.verificationRoot);
|
|
578
|
+
}
|
|
579
|
+
this.print('');
|
|
580
|
+
this.print('Type /help for available commands, or just describe your task.');
|
|
581
|
+
this.print('');
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Print help
|
|
585
|
+
*/
|
|
586
|
+
printHelp() {
|
|
587
|
+
this.print('');
|
|
588
|
+
this.print('PM Orchestrator Runner - Commands');
|
|
589
|
+
this.print('=================================');
|
|
590
|
+
this.print('');
|
|
591
|
+
this.print(' /help Show this help message');
|
|
592
|
+
this.print(' /init Initialize .claude/ directory in current project');
|
|
593
|
+
this.print('');
|
|
594
|
+
this.print('Provider/Model Configuration:');
|
|
595
|
+
this.print(' /provider Show current provider');
|
|
596
|
+
this.print(' /provider show List all available providers');
|
|
597
|
+
this.print(' /provider <name> Set provider (claude-code, openai, anthropic)');
|
|
598
|
+
this.print(' /models List models for current provider');
|
|
599
|
+
this.print(' /models <provider> List models for specific provider');
|
|
600
|
+
this.print(' /model Show current model configuration');
|
|
601
|
+
this.print(' /model <name> Set model (saves to .claude/repl.json)');
|
|
602
|
+
this.print(' /keys Show API key status (SET/NOT SET)');
|
|
603
|
+
this.print('');
|
|
604
|
+
this.print('Session Management:');
|
|
605
|
+
this.print(' /start [path] Start a new session (optional project path)');
|
|
606
|
+
this.print(' /continue [session] Continue a paused session');
|
|
607
|
+
this.print(' /status Show session status and phase');
|
|
608
|
+
this.print(' /tasks Show tasks in current session');
|
|
609
|
+
this.print(' /approve Approve continuation for INCOMPLETE session');
|
|
610
|
+
this.print('');
|
|
611
|
+
this.print('Logging:');
|
|
612
|
+
this.print(' /logs List task logs for current session');
|
|
613
|
+
this.print(' /logs <task-id> Show task details (summary view)');
|
|
614
|
+
this.print(' /logs <task-id> --full Show task details with executor logs');
|
|
615
|
+
this.print('');
|
|
616
|
+
this.print('Other:');
|
|
617
|
+
this.print(' /exit Exit REPL (saves state)');
|
|
618
|
+
this.print('');
|
|
619
|
+
this.print('Natural Language:');
|
|
620
|
+
this.print(' Just type your task description without a slash prefix.');
|
|
621
|
+
this.print(' Example: "Create an HTTP server with /health endpoint"');
|
|
622
|
+
this.print('');
|
|
623
|
+
this.print('Auth Mode: ' + this.config.authMode);
|
|
624
|
+
this.print(' L1 (read-only subagents) use Claude Code CLI - no API key needed.');
|
|
625
|
+
this.print(' L2 (executor) also uses Claude Code CLI integration.');
|
|
626
|
+
this.print('');
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Handle /init command
|
|
630
|
+
*/
|
|
631
|
+
async handleInit(args) {
|
|
632
|
+
const targetPath = args[0] || this.session.projectPath;
|
|
633
|
+
const result = await this.initCommand.execute(targetPath);
|
|
634
|
+
if (result.success) {
|
|
635
|
+
this.print(result.message);
|
|
636
|
+
this.print('');
|
|
637
|
+
this.print('Next: /start to begin a session');
|
|
638
|
+
return { success: true };
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
this.print('Error: ' + result.message);
|
|
642
|
+
return {
|
|
643
|
+
success: false,
|
|
644
|
+
error: {
|
|
645
|
+
code: 'E101',
|
|
646
|
+
message: result.message,
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Handle /model command
|
|
653
|
+
* Per spec 10_REPL_UX.md L113-143:
|
|
654
|
+
* - /model displays current model or "UNSET"
|
|
655
|
+
* - /model <name> sets model and generates Evidence
|
|
656
|
+
* - .claude/ missing -> E101 ERROR
|
|
657
|
+
* - JSON parse error -> E105 ERROR
|
|
658
|
+
*/
|
|
659
|
+
async handleModel(args) {
|
|
660
|
+
if (args.length === 0) {
|
|
661
|
+
const result = await this.modelCommand.getModel(this.session.projectPath);
|
|
662
|
+
if (result.success) {
|
|
663
|
+
// Per spec 10_REPL_UX.md L133: Display "UNSET" if not configured
|
|
664
|
+
this.print('Current model: ' + result.model);
|
|
665
|
+
if (result.configPath) {
|
|
666
|
+
this.print('Config file: ' + result.configPath);
|
|
667
|
+
}
|
|
668
|
+
return { success: true };
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// Per spec 10_REPL_UX.md L137-138: E101 or E105
|
|
672
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
673
|
+
return {
|
|
674
|
+
success: false,
|
|
675
|
+
error: result.error || { code: 'E105', message: result.message || 'Unknown error' },
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
const result = await this.modelCommand.setModel(this.session.projectPath, args[0]);
|
|
681
|
+
if (result.success) {
|
|
682
|
+
this.print('Model set to: ' + args[0]);
|
|
683
|
+
this.print('Saved to: ' + result.configPath);
|
|
684
|
+
if (result.evidencePath) {
|
|
685
|
+
this.print('Evidence: ' + result.evidencePath);
|
|
686
|
+
}
|
|
687
|
+
return { success: true };
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
// Per spec 10_REPL_UX.md L137: E101 if .claude/ missing
|
|
691
|
+
const errorCode = result.error?.code || 'E102';
|
|
692
|
+
const errorMessage = result.error?.message || result.message || 'Failed to set model';
|
|
693
|
+
this.print('Error [' + errorCode + ']: ' + errorMessage);
|
|
694
|
+
return {
|
|
695
|
+
success: false,
|
|
696
|
+
error: { code: errorCode, message: errorMessage },
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Handle /provider command
|
|
703
|
+
* Per spec 10_REPL_UX.md Section 2.1
|
|
704
|
+
*/
|
|
705
|
+
async handleProvider(args) {
|
|
706
|
+
if (args.length === 0) {
|
|
707
|
+
// /provider - show current
|
|
708
|
+
const result = await this.providerCommand.getProvider(this.session.projectPath);
|
|
709
|
+
if (result.success) {
|
|
710
|
+
const output = this.providerCommand.formatCurrentProvider(result.provider || 'UNSET');
|
|
711
|
+
this.print(output);
|
|
712
|
+
return { success: true };
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
716
|
+
return { success: false, error: result.error };
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else if (args[0] === 'show') {
|
|
720
|
+
// /provider show - list all
|
|
721
|
+
const result = await this.providerCommand.listProviders();
|
|
722
|
+
const currentResult = await this.providerCommand.getProvider(this.session.projectPath);
|
|
723
|
+
const currentProvider = currentResult.success ? currentResult.provider : undefined;
|
|
724
|
+
const output = this.providerCommand.formatProviderList(result.providers || [], currentProvider);
|
|
725
|
+
this.print(output);
|
|
726
|
+
return { success: true };
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
// /provider <name> - set provider
|
|
730
|
+
const result = await this.providerCommand.setProvider(this.session.projectPath, args[0]);
|
|
731
|
+
if (result.success) {
|
|
732
|
+
this.print('Provider set to: ' + args[0]);
|
|
733
|
+
this.print('Saved to: ' + result.configPath);
|
|
734
|
+
this.print('Note: Model selection has been reset. Use /models to see available models.');
|
|
735
|
+
if (result.evidencePath) {
|
|
736
|
+
this.print('Evidence: ' + result.evidencePath);
|
|
737
|
+
}
|
|
738
|
+
return { success: true };
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
742
|
+
return { success: false, error: result.error };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Handle /models command
|
|
748
|
+
* Per spec 10_REPL_UX.md Section 2.2
|
|
749
|
+
*/
|
|
750
|
+
async handleModels(args) {
|
|
751
|
+
const providerId = args.length > 0 ? args[0] : undefined;
|
|
752
|
+
const result = await this.modelsCommand.listModels(this.session.projectPath, providerId);
|
|
753
|
+
if (result.success) {
|
|
754
|
+
const output = this.modelsCommand.formatModelList(result.models || [], result.currentModel, result.provider);
|
|
755
|
+
this.print(output);
|
|
756
|
+
return { success: true };
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
760
|
+
return { success: false, error: result.error };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Handle /keys command
|
|
765
|
+
* Per spec 10_REPL_UX.md Section 2.3
|
|
766
|
+
*/
|
|
767
|
+
async handleKeys(args) {
|
|
768
|
+
let result;
|
|
769
|
+
if (args.length > 0) {
|
|
770
|
+
result = await this.keysCommand.checkProviderKey(args[0]);
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
result = await this.keysCommand.getKeyStatus();
|
|
774
|
+
}
|
|
775
|
+
if (result.success) {
|
|
776
|
+
const output = this.keysCommand.formatKeyStatus(result.keys || []);
|
|
777
|
+
this.print(output);
|
|
778
|
+
return { success: true };
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
782
|
+
return { success: false, error: result.error };
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Handle /logs command
|
|
787
|
+
* Per spec 10_REPL_UX.md Section 2.4
|
|
788
|
+
*/
|
|
789
|
+
async handleLogs(args) {
|
|
790
|
+
if (args.length === 0) {
|
|
791
|
+
// /logs - list all logs for current session
|
|
792
|
+
if (!this.session.sessionId) {
|
|
793
|
+
this.print('No active session. Use /start to begin a session.');
|
|
794
|
+
return {
|
|
795
|
+
success: false,
|
|
796
|
+
error: { code: 'E104', message: 'No active session' },
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const result = await this.logsCommand.listLogs(this.session.projectPath, this.session.sessionId);
|
|
800
|
+
if (result.success) {
|
|
801
|
+
this.print(result.output || 'No logs found.');
|
|
802
|
+
return { success: true };
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
806
|
+
return { success: false, error: result.error };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
// /logs <task-id> [--full]
|
|
811
|
+
const taskId = args[0];
|
|
812
|
+
const full = args.includes('--full');
|
|
813
|
+
const result = await this.logsCommand.getTaskDetail(this.session.projectPath, taskId, full);
|
|
814
|
+
if (result.success) {
|
|
815
|
+
this.print(result.output || 'No log detail found.');
|
|
816
|
+
return { success: true };
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
this.print('Error [' + result.error?.code + ']: ' + (result.error?.message || result.message));
|
|
820
|
+
return { success: false, error: result.error };
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Handle /tasks command
|
|
826
|
+
*/
|
|
827
|
+
async handleTasks() {
|
|
828
|
+
const result = await this.statusCommands.getTasks();
|
|
829
|
+
this.print(result);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Handle /status command
|
|
833
|
+
*/
|
|
834
|
+
async handleStatus() {
|
|
835
|
+
const result = await this.statusCommands.getStatus();
|
|
836
|
+
this.print(result);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Handle /start command
|
|
840
|
+
* Per spec Property 32, 33: Use verification_root for file operations in temp mode
|
|
841
|
+
*/
|
|
842
|
+
async handleStart(args) {
|
|
843
|
+
// In temp mode, use verificationRoot for file operations
|
|
844
|
+
// In fixed mode or with explicit args, use the provided/session path
|
|
845
|
+
const projectPath = args[0] ||
|
|
846
|
+
(this.projectMode === 'temp' ? this.verificationRoot : this.session.projectPath);
|
|
847
|
+
try {
|
|
848
|
+
const result = await this.sessionCommands.start(projectPath);
|
|
849
|
+
if (result.success) {
|
|
850
|
+
this.session.sessionId = result.sessionId;
|
|
851
|
+
this.session.runner = result.runner;
|
|
852
|
+
this.session.status = 'running';
|
|
853
|
+
// Keep session.projectPath as verificationRoot in temp mode
|
|
854
|
+
this.session.projectPath = projectPath;
|
|
855
|
+
// Initialize supervisor
|
|
856
|
+
const supervisorConfig = {
|
|
857
|
+
checkIntervalMs: 5000,
|
|
858
|
+
maxRetries: 3,
|
|
859
|
+
timeoutMs: this.config.evidenceDir ? 300000 : 300000,
|
|
860
|
+
};
|
|
861
|
+
this.session.supervisor = new executor_supervisor_1.ExecutorSupervisor(this.session.runner, supervisorConfig);
|
|
862
|
+
this.print('Session started: ' + result.sessionId);
|
|
863
|
+
this.print('Project: ' + projectPath);
|
|
864
|
+
this.print('');
|
|
865
|
+
this.print('You can now describe tasks in natural language.');
|
|
866
|
+
return { success: true };
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
this.print('Error: ' + result.message);
|
|
870
|
+
return {
|
|
871
|
+
success: false,
|
|
872
|
+
error: { code: 'E103', message: result.message || 'Failed to start session' },
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
catch (err) {
|
|
877
|
+
this.printError(err);
|
|
878
|
+
return {
|
|
879
|
+
success: false,
|
|
880
|
+
error: { code: 'E103', message: err.message },
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Handle /continue command
|
|
886
|
+
*/
|
|
887
|
+
async handleContinue(args) {
|
|
888
|
+
const sessionId = args[0] || this.session.sessionId;
|
|
889
|
+
if (!sessionId) {
|
|
890
|
+
this.print('No session to continue. Provide a session ID or start a new session.');
|
|
891
|
+
return {
|
|
892
|
+
success: false,
|
|
893
|
+
error: { code: 'E104', message: 'No session to continue' },
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const result = await this.sessionCommands.continueSession(sessionId);
|
|
898
|
+
if (result.success) {
|
|
899
|
+
this.session.sessionId = sessionId;
|
|
900
|
+
this.session.runner = result.runner;
|
|
901
|
+
this.session.status = 'running';
|
|
902
|
+
this.print('Session resumed: ' + sessionId);
|
|
903
|
+
return { success: true };
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
this.print('Error: ' + result.message);
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
error: { code: 'E104', message: result.message || 'Failed to continue session' },
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
catch (err) {
|
|
914
|
+
this.printError(err);
|
|
915
|
+
return {
|
|
916
|
+
success: false,
|
|
917
|
+
error: { code: 'E104', message: err.message },
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Handle /approve command
|
|
923
|
+
*/
|
|
924
|
+
async handleApprove() {
|
|
925
|
+
if (!this.session.sessionId) {
|
|
926
|
+
this.print('No active session to approve.');
|
|
927
|
+
return {
|
|
928
|
+
success: false,
|
|
929
|
+
error: { code: 'E106', message: 'No active session to approve' },
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
const result = await this.sessionCommands.approve(this.session.sessionId);
|
|
934
|
+
if (result.success) {
|
|
935
|
+
this.print('Continuation approved.');
|
|
936
|
+
this.print('Use /continue to resume execution.');
|
|
937
|
+
return { success: true };
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
this.print('Error: ' + result.message);
|
|
941
|
+
return {
|
|
942
|
+
success: false,
|
|
943
|
+
error: { code: 'E106', message: result.message || 'Failed to approve' },
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
catch (err) {
|
|
948
|
+
this.printError(err);
|
|
949
|
+
return {
|
|
950
|
+
success: false,
|
|
951
|
+
error: { code: 'E106', message: err.message },
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Handle /exit command
|
|
957
|
+
* Per spec 10_REPL_UX.md: Ensure clean exit with flushed output
|
|
958
|
+
*
|
|
959
|
+
* Guarantees:
|
|
960
|
+
* - Session state is persisted before exit
|
|
961
|
+
* - All output is flushed before readline closes
|
|
962
|
+
* - Double-completion is prevented via sessionCompleted flag
|
|
963
|
+
*/
|
|
964
|
+
async handleExit() {
|
|
965
|
+
// Prevent double completion (e.g., if /exit is called multiple times)
|
|
966
|
+
if (this.sessionCompleted) {
|
|
967
|
+
this.print('Session already completed, closing...');
|
|
968
|
+
this.running = false;
|
|
969
|
+
this.rl?.close();
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
this.sessionCompleted = true;
|
|
973
|
+
this.print('Saving session state...');
|
|
974
|
+
if (this.session.runner && this.session.sessionId) {
|
|
975
|
+
try {
|
|
976
|
+
// Complete the session first (updates status from RUNNING to COMPLETED/FAILED)
|
|
977
|
+
const failed = this.hasError || this.hasIncompleteTasks;
|
|
978
|
+
await this.session.runner.completeSession(failed);
|
|
979
|
+
await this.session.runner.saveState();
|
|
980
|
+
this.print('Session saved: ' + this.session.sessionId);
|
|
981
|
+
}
|
|
982
|
+
catch (err) {
|
|
983
|
+
// Log error but ensure status is updated for exit code calculation
|
|
984
|
+
const errorMessage = err.message || String(err);
|
|
985
|
+
this.print('Warning: Could not save session state: ' + errorMessage);
|
|
986
|
+
// Mark as error so exit code reflects persistence failure
|
|
987
|
+
this.hasError = true;
|
|
988
|
+
this.updateExitCode();
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (this.session.supervisor) {
|
|
992
|
+
this.session.supervisor.stop();
|
|
993
|
+
}
|
|
994
|
+
this.print('Goodbye!');
|
|
995
|
+
// Flush output before closing (critical for non-interactive mode)
|
|
996
|
+
await this.flushStdout();
|
|
997
|
+
this.running = false;
|
|
998
|
+
this.rl?.close();
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Print execution result
|
|
1002
|
+
* Per spec 10_REPL_UX.md: Error details must be visible for fail-closed debugging
|
|
1003
|
+
*/
|
|
1004
|
+
printExecutionResult(result) {
|
|
1005
|
+
this.print('');
|
|
1006
|
+
this.print('--- Execution Result ---');
|
|
1007
|
+
this.print('Status: ' + result.overall_status);
|
|
1008
|
+
if (result.tasks_completed !== undefined) {
|
|
1009
|
+
this.print('Tasks: ' + result.tasks_completed + '/' + result.tasks_total + ' completed');
|
|
1010
|
+
// Track incomplete tasks for exit code (non-interactive mode)
|
|
1011
|
+
if (result.tasks_completed < (result.tasks_total || 0)) {
|
|
1012
|
+
this.hasIncompleteTasks = true;
|
|
1013
|
+
this.updateExitCode();
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// Display error details when status is ERROR (critical for debugging fail-closed behavior)
|
|
1017
|
+
if (result.error) {
|
|
1018
|
+
this.print('Error: ' + result.error.message);
|
|
1019
|
+
if (result.error.code) {
|
|
1020
|
+
this.print('Error Code: ' + result.error.code);
|
|
1021
|
+
}
|
|
1022
|
+
// Track error for exit code
|
|
1023
|
+
this.hasError = true;
|
|
1024
|
+
this.updateExitCode();
|
|
1025
|
+
}
|
|
1026
|
+
// Display incomplete task reasons (helps identify why tasks failed)
|
|
1027
|
+
if (result.incomplete_task_reasons && result.incomplete_task_reasons.length > 0) {
|
|
1028
|
+
this.print('Incomplete Tasks:');
|
|
1029
|
+
for (const reason of result.incomplete_task_reasons) {
|
|
1030
|
+
this.print(' - ' + reason.task_id + ': ' + reason.reason);
|
|
1031
|
+
}
|
|
1032
|
+
this.hasIncompleteTasks = true;
|
|
1033
|
+
this.updateExitCode();
|
|
1034
|
+
}
|
|
1035
|
+
// Track ERROR status
|
|
1036
|
+
if (result.overall_status === enums_1.OverallStatus.ERROR) {
|
|
1037
|
+
this.hasError = true;
|
|
1038
|
+
this.updateExitCode();
|
|
1039
|
+
}
|
|
1040
|
+
// Track INCOMPLETE status
|
|
1041
|
+
if (result.overall_status === enums_1.OverallStatus.INCOMPLETE) {
|
|
1042
|
+
this.hasIncompleteTasks = true;
|
|
1043
|
+
this.updateExitCode();
|
|
1044
|
+
}
|
|
1045
|
+
if (result.next_action !== undefined) {
|
|
1046
|
+
this.print('Next Action: ' + (result.next_action ? 'Yes' : 'No'));
|
|
1047
|
+
if (result.next_action_reason) {
|
|
1048
|
+
this.print('Reason: ' + result.next_action_reason);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
this.print('------------------------');
|
|
1052
|
+
this.print('');
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Print message with flush guarantee for non-interactive mode
|
|
1056
|
+
* Per spec 10_REPL_UX.md: Output Flush Guarantee
|
|
1057
|
+
*/
|
|
1058
|
+
print(message) {
|
|
1059
|
+
console.log(message);
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Flush stdout - ensures all output is written before continuing
|
|
1063
|
+
* Critical for non-interactive mode where process may exit immediately after
|
|
1064
|
+
*/
|
|
1065
|
+
async flushStdout() {
|
|
1066
|
+
return new Promise((resolve) => {
|
|
1067
|
+
// If stdout is already drained, resolve immediately
|
|
1068
|
+
if (process.stdout.write('')) {
|
|
1069
|
+
resolve();
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
// Wait for drain event
|
|
1073
|
+
process.stdout.once('drain', () => {
|
|
1074
|
+
resolve();
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Print error
|
|
1081
|
+
*/
|
|
1082
|
+
printError(err) {
|
|
1083
|
+
console.error('Error: ' + err.message);
|
|
1084
|
+
this.hasError = true;
|
|
1085
|
+
this.updateExitCode();
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Validate project structure - per spec 10_REPL_UX.md L45
|
|
1089
|
+
* "REPL の起動時点で validate 相当の検証を行い、必須構造がなければ即 ERROR とする。"
|
|
1090
|
+
*
|
|
1091
|
+
* @returns Validation result with valid flag and errors array
|
|
1092
|
+
*/
|
|
1093
|
+
async validateProjectStructure() {
|
|
1094
|
+
const errors = [];
|
|
1095
|
+
const projectPath = this.config.projectPath;
|
|
1096
|
+
// Check .claude directory exists
|
|
1097
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
1098
|
+
if (!fs.existsSync(claudeDir)) {
|
|
1099
|
+
errors.push('.claude directory not found at ' + claudeDir);
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
// Check for required files/directories within .claude
|
|
1103
|
+
const requiredItems = [
|
|
1104
|
+
{ path: path.join(claudeDir, 'CLAUDE.md'), type: 'file', name: 'CLAUDE.md' },
|
|
1105
|
+
{ path: path.join(claudeDir, 'settings.json'), type: 'file', name: 'settings.json' },
|
|
1106
|
+
];
|
|
1107
|
+
for (const item of requiredItems) {
|
|
1108
|
+
if (!fs.existsSync(item.path)) {
|
|
1109
|
+
errors.push('Required ' + item.type + ' not found: ' + item.name);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
valid: errors.length === 0,
|
|
1115
|
+
errors,
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Check if REPL is in init-only mode
|
|
1120
|
+
* Init-only mode is active when .claude directory is missing
|
|
1121
|
+
* In this mode, only /help, /init, /exit are allowed
|
|
1122
|
+
*
|
|
1123
|
+
* @returns true if in init-only mode (rechecks file system)
|
|
1124
|
+
*/
|
|
1125
|
+
async isInitOnlyMode() {
|
|
1126
|
+
const validation = await this.validateProjectStructure();
|
|
1127
|
+
this.initOnlyMode = !validation.valid;
|
|
1128
|
+
return this.initOnlyMode;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Tab completion for slash commands
|
|
1132
|
+
* Per spec 10_REPL_UX.md: Tab completion support
|
|
1133
|
+
*
|
|
1134
|
+
* @param line - Current input line
|
|
1135
|
+
* @returns Tuple of [completions, original line]
|
|
1136
|
+
*/
|
|
1137
|
+
completer(line) {
|
|
1138
|
+
// Only complete if line starts with /
|
|
1139
|
+
if (!line.startsWith('/')) {
|
|
1140
|
+
return [[], line];
|
|
1141
|
+
}
|
|
1142
|
+
// Find matching commands (prefix match)
|
|
1143
|
+
const hits = SLASH_COMMANDS.filter(cmd => cmd.startsWith(line));
|
|
1144
|
+
// Return only matching commands (empty if no match)
|
|
1145
|
+
return [hits, line];
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Detect execution mode based on TTY, environment, or config
|
|
1149
|
+
* Per spec 10_REPL_UX.md: Non-interactive mode detection
|
|
1150
|
+
*/
|
|
1151
|
+
detectExecutionMode() {
|
|
1152
|
+
// Check config override first
|
|
1153
|
+
if (this.config.forceNonInteractive) {
|
|
1154
|
+
return 'non_interactive';
|
|
1155
|
+
}
|
|
1156
|
+
// Check environment variable (for testing)
|
|
1157
|
+
if (process.env.PM_RUNNER_NON_INTERACTIVE === '1') {
|
|
1158
|
+
return 'non_interactive';
|
|
1159
|
+
}
|
|
1160
|
+
// Primary detection: stdin TTY check
|
|
1161
|
+
// stdin.isTTY is undefined when piped or from heredoc
|
|
1162
|
+
if (!process.stdin.isTTY) {
|
|
1163
|
+
return 'non_interactive';
|
|
1164
|
+
}
|
|
1165
|
+
return 'interactive';
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Check if running in non-interactive mode
|
|
1169
|
+
*/
|
|
1170
|
+
isNonInteractiveMode() {
|
|
1171
|
+
return this.executionMode === 'non_interactive';
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Get the current execution mode
|
|
1175
|
+
*/
|
|
1176
|
+
getExecutionMode() {
|
|
1177
|
+
return this.executionMode;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Set exit code based on current state
|
|
1181
|
+
* Per spec 10_REPL_UX.md: Deterministic exit codes
|
|
1182
|
+
*/
|
|
1183
|
+
updateExitCode() {
|
|
1184
|
+
if (this.hasError) {
|
|
1185
|
+
this.exitCode = exports.EXIT_CODES.ERROR;
|
|
1186
|
+
}
|
|
1187
|
+
else if (this.hasIncompleteTasks) {
|
|
1188
|
+
this.exitCode = exports.EXIT_CODES.INCOMPLETE;
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
this.exitCode = exports.EXIT_CODES.COMPLETE;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Get the exit code (for non-interactive mode)
|
|
1196
|
+
*/
|
|
1197
|
+
getExitCode() {
|
|
1198
|
+
return this.exitCode;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Get session state (for testing)
|
|
1202
|
+
*/
|
|
1203
|
+
getSessionState() {
|
|
1204
|
+
return { ...this.session };
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Check if running (for testing)
|
|
1208
|
+
*/
|
|
1209
|
+
isRunning() {
|
|
1210
|
+
return this.running;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
exports.REPLInterface = REPLInterface;
|
|
1214
|
+
//# sourceMappingURL=repl-interface.js.map
|