nubos-pilot 0.5.2 → 0.5.4
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/bin/install.js +2 -12
- package/bin/np-tools/_commands.cjs +2 -0
- package/bin/np-tools/add-tests.cjs +4 -0
- package/bin/np-tools/add-todo.cjs +4 -0
- package/bin/np-tools/discuss-phase.cjs +5 -0
- package/bin/np-tools/discuss-phase.test.cjs +75 -18
- package/bin/np-tools/discuss-project.cjs +5 -0
- package/bin/np-tools/execute-milestone.cjs +5 -0
- package/bin/np-tools/lang-directive.cjs +64 -0
- package/bin/np-tools/lang-directive.test.cjs +88 -0
- package/bin/np-tools/new-milestone.cjs +6 -2
- package/bin/np-tools/new-project.cjs +6 -2
- package/bin/np-tools/plan-milestone.cjs +5 -0
- package/bin/np-tools/research-phase.cjs +5 -0
- package/bin/np-tools/resume-work.cjs +5 -0
- package/bin/np-tools/text-mode.cjs +56 -0
- package/bin/np-tools/text-mode.test.cjs +132 -0
- package/bin/np-tools/verify-work.cjs +5 -0
- package/lib/language.cjs +63 -0
- package/lib/language.test.cjs +99 -0
- package/lib/runtime/_readline.cjs +5 -1
- package/lib/runtime/_readline.test.cjs +2 -1
- package/lib/runtime/claude.cjs +21 -1
- package/lib/runtime/claude.test.cjs +63 -0
- package/lib/text-mode.cjs +79 -0
- package/lib/text-mode.test.cjs +139 -0
- package/np-tools.cjs +2 -0
- package/package.json +1 -1
- package/workflows/add-todo.md +10 -5
- package/workflows/discuss-phase.md +49 -17
- package/workflows/discuss-project.md +6 -0
- package/workflows/execute-phase.md +16 -2
- package/workflows/new-milestone.md +12 -0
- package/workflows/new-project.md +13 -0
- package/workflows/note.md +11 -0
- package/workflows/pause-work.md +5 -0
- package/workflows/plan-phase.md +13 -1
- package/workflows/research-phase.md +12 -0
- package/workflows/resume-work.md +12 -0
- package/workflows/scan-codebase.md +8 -0
- package/workflows/session-report.md +18 -0
- package/workflows/update-docs.md +7 -0
- package/workflows/validate-phase.md +13 -1
- package/workflows/verify-work.md +15 -1
package/bin/install.js
CHANGED
|
@@ -16,6 +16,7 @@ const runtimeDetectMod = require('../lib/install/runtime-detect.cjs');
|
|
|
16
16
|
const backupMod = require('../lib/install/backup.cjs');
|
|
17
17
|
const registryMod = require('../lib/install/runtimes-registry.cjs');
|
|
18
18
|
const runtimeAssetsMod = require('../lib/install/runtime-assets.cjs');
|
|
19
|
+
const languageMod = require('../lib/language.cjs');
|
|
19
20
|
|
|
20
21
|
const cyan = '\x1b[36m', green = '\x1b[32m', yellow = '\x1b[33m',
|
|
21
22
|
red = '\x1b[31m', blue = '\x1b[38;5;33m',
|
|
@@ -64,22 +65,11 @@ function _autoAskUser(spec) {
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
const LANG_DIRECTIVES = {
|
|
68
|
-
de: 'Sprache: **Deutsch.** Jede nubos-pilot Slash-Command-Ausgabe, jede Frage an den User und jedes Statusupdate in allen `/np:*` Workflows ist auf Deutsch zu schreiben — inklusive Fehlermeldungen und Klärungsfragen. Nur Code, Bash-Kommandos, Tool-Outputs und Commit-Messages bleiben wie sie sind.',
|
|
69
|
-
en: 'Language: **English.** All `/np:*` slash-command output, askuser prompts and status updates respond in English.',
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
function _langDirective(responseLanguage) {
|
|
73
|
-
const lang = String(responseLanguage || 'en').toLowerCase();
|
|
74
|
-
if (LANG_DIRECTIVES[lang]) return LANG_DIRECTIVES[lang];
|
|
75
|
-
return 'Language: respond in the ISO-639 language `' + lang + '` for all `/np:*` slash-command output, askuser prompts and status updates.';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
68
|
function _managedBlockInner(responseLanguage) {
|
|
79
69
|
return (
|
|
80
70
|
'This project uses [nubos-pilot](https://github.com/nubos/nubos-pilot)'
|
|
81
71
|
+ ' for planning and execution.\n\n'
|
|
82
|
-
+
|
|
72
|
+
+ languageMod.buildDirective(responseLanguage)
|
|
83
73
|
+ '\n\nRun `npx nubos-pilot doctor` to check install integrity.'
|
|
84
74
|
);
|
|
85
75
|
}
|
|
@@ -43,6 +43,8 @@ const COMMANDS = [
|
|
|
43
43
|
{ name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)' },
|
|
44
44
|
{ name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard' },
|
|
45
45
|
{ name: 'config-get', category: 'Utility', description: 'Read value from .nubos-pilot/config.json by dotted key path' },
|
|
46
|
+
{ name: 'lang-directive', category: 'Utility', description: 'Print workflow language directive from config.response_language (SSOT)' },
|
|
47
|
+
{ name: 'text-mode', category: 'Utility', description: 'Print whether text mode is active (config.workflow.text_mode ∨ CLAUDECODE)' },
|
|
46
48
|
{ name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify' },
|
|
47
49
|
{ name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
|
|
48
50
|
|
|
@@ -5,6 +5,7 @@ const { NubosPilotError, atomicWriteFileSync, withFileLock } = require('../../li
|
|
|
5
5
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
6
6
|
const layout = require('../../lib/layout.cjs');
|
|
7
7
|
const { parseVerificationMd, milestoneVerificationPath } = require('../../lib/verify.cjs');
|
|
8
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
8
9
|
|
|
9
10
|
const BEGIN_MARKER = '// >>> np:add-tests begin';
|
|
10
11
|
const END_MARKER = '// <<< np:add-tests end';
|
|
@@ -138,6 +139,7 @@ function run(args, ctx) {
|
|
|
138
139
|
const mNum = _validateMilestoneArg(list[1]);
|
|
139
140
|
const target = _resolveTestTarget(mNum, cwd);
|
|
140
141
|
const { passes, skips, verification_path } = _loadCases(mNum, cwd);
|
|
142
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
141
143
|
const payload = {
|
|
142
144
|
_workflow: 'add-tests',
|
|
143
145
|
milestone: mNum,
|
|
@@ -146,6 +148,8 @@ function run(args, ctx) {
|
|
|
146
148
|
verification_path,
|
|
147
149
|
pass_cases: passes,
|
|
148
150
|
skip_cases: skips,
|
|
151
|
+
text_mode: tmDetail.enabled,
|
|
152
|
+
text_mode_source: tmDetail.source,
|
|
149
153
|
};
|
|
150
154
|
stdout.write(JSON.stringify(payload, null, 2));
|
|
151
155
|
return payload;
|
|
@@ -3,6 +3,7 @@ const path = require('node:path');
|
|
|
3
3
|
|
|
4
4
|
const { projectStateDir, NubosPilotError } = require('../../lib/core.cjs');
|
|
5
5
|
const { slugify } = require('../../lib/layout.cjs');
|
|
6
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
6
7
|
|
|
7
8
|
const MAX_DESCRIPTION_LENGTH = 500;
|
|
8
9
|
|
|
@@ -54,6 +55,7 @@ function _buildPayload(description, cwd) {
|
|
|
54
55
|
}
|
|
55
56
|
const todos_dir_exists = fs.existsSync(todosDir);
|
|
56
57
|
const state_path = path.join(stateDir, 'STATE.md');
|
|
58
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
57
59
|
return {
|
|
58
60
|
_workflow: 'add-todo',
|
|
59
61
|
commit_docs: true,
|
|
@@ -66,6 +68,8 @@ function _buildPayload(description, cwd) {
|
|
|
66
68
|
todos_dir_exists,
|
|
67
69
|
todo_count,
|
|
68
70
|
state_path,
|
|
71
|
+
text_mode: tmDetail.enabled,
|
|
72
|
+
text_mode_source: tmDetail.source,
|
|
69
73
|
todos: [],
|
|
70
74
|
};
|
|
71
75
|
}
|
|
@@ -8,6 +8,7 @@ const crypto = require('node:crypto');
|
|
|
8
8
|
const { NubosPilotError, projectStateDir } = require('../../lib/core.cjs');
|
|
9
9
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
10
10
|
const layout = require('../../lib/layout.cjs');
|
|
11
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
11
12
|
|
|
12
13
|
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
13
14
|
|
|
@@ -96,6 +97,8 @@ function run(args, ctx) {
|
|
|
96
97
|
const has_context = fs.existsSync(contextPath);
|
|
97
98
|
const has_milestone_dir = fs.existsSync(milestoneDir);
|
|
98
99
|
|
|
100
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
101
|
+
|
|
99
102
|
const payload = {
|
|
100
103
|
_workflow: 'discuss-phase',
|
|
101
104
|
milestone: mNum,
|
|
@@ -109,6 +112,8 @@ function run(args, ctx) {
|
|
|
109
112
|
requirements: Array.isArray(def.requirements) ? def.requirements : [],
|
|
110
113
|
success_criteria: Array.isArray(def.success_criteria) ? def.success_criteria : [],
|
|
111
114
|
mode: flags.assumptions ? 'assumptions' : 'adaptive',
|
|
115
|
+
text_mode: tmDetail.enabled,
|
|
116
|
+
text_mode_source: tmDetail.source,
|
|
112
117
|
agent_skills: _agentSkills(),
|
|
113
118
|
};
|
|
114
119
|
|
|
@@ -7,6 +7,20 @@ const { makeSandbox, seedRoadmapYaml, cleanupAll } =
|
|
|
7
7
|
require('../../tests/helpers/fixture.cjs');
|
|
8
8
|
const subcmd = require('./discuss-phase.cjs');
|
|
9
9
|
|
|
10
|
+
function _clearClaudeEnv() {
|
|
11
|
+
const saved = {};
|
|
12
|
+
for (const k of ['CLAUDECODE', 'CLAUDE_CODE_ENTRYPOINT']) {
|
|
13
|
+
saved[k] = process.env[k];
|
|
14
|
+
delete process.env[k];
|
|
15
|
+
}
|
|
16
|
+
return () => {
|
|
17
|
+
for (const [k, v] of Object.entries(saved)) {
|
|
18
|
+
if (v === undefined) delete process.env[k];
|
|
19
|
+
else process.env[k] = v;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
function _baseRoadmap() {
|
|
11
25
|
return {
|
|
12
26
|
schema_version: 1,
|
|
@@ -42,24 +56,67 @@ function _captureStdout() {
|
|
|
42
56
|
afterEach(cleanupAll);
|
|
43
57
|
|
|
44
58
|
test('DP-1: run(["3"]) on valid milestone returns JSON payload with expected shape', () => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const restore = _clearClaudeEnv();
|
|
60
|
+
try {
|
|
61
|
+
const sandbox = makeSandbox();
|
|
62
|
+
seedRoadmapYaml(sandbox, _baseRoadmap());
|
|
63
|
+
const cap = _captureStdout();
|
|
64
|
+
subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
|
|
65
|
+
const raw = cap.get().trim();
|
|
66
|
+
assert.ok(!raw.startsWith('@file:'));
|
|
67
|
+
const payload = JSON.parse(raw);
|
|
68
|
+
assert.equal(payload.milestone, 3);
|
|
69
|
+
assert.equal(payload.milestone_id, 'M003');
|
|
70
|
+
assert.ok(payload.milestone_dir.endsWith(path.join('.nubos-pilot', 'milestones', 'M003')));
|
|
71
|
+
assert.ok(payload.milestone_context_path.endsWith(path.join('M003', 'M003-CONTEXT.md')));
|
|
72
|
+
assert.equal(payload.milestone_name, 'Observability');
|
|
73
|
+
assert.equal(payload.has_context, false);
|
|
74
|
+
assert.equal(payload.has_milestone_dir, false);
|
|
75
|
+
assert.equal(payload.goal, 'Ship structured logging + metrics');
|
|
76
|
+
assert.deepEqual(payload.requirements, ['OBS-01']);
|
|
77
|
+
assert.ok('agent_skills' in payload);
|
|
78
|
+
assert.equal(payload.mode, 'adaptive');
|
|
79
|
+
assert.equal(payload.text_mode, false);
|
|
80
|
+
assert.equal(payload.text_mode_source, 'default');
|
|
81
|
+
} finally {
|
|
82
|
+
restore();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('DP-1b: CLAUDECODE=1 sets text_mode=true with runtime source', () => {
|
|
87
|
+
const restore = _clearClaudeEnv();
|
|
88
|
+
try {
|
|
89
|
+
process.env.CLAUDECODE = '1';
|
|
90
|
+
const sandbox = makeSandbox();
|
|
91
|
+
seedRoadmapYaml(sandbox, _baseRoadmap());
|
|
92
|
+
const cap = _captureStdout();
|
|
93
|
+
subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
|
|
94
|
+
const payload = JSON.parse(cap.get().trim());
|
|
95
|
+
assert.equal(payload.text_mode, true);
|
|
96
|
+
assert.equal(payload.text_mode_source, 'runtime');
|
|
97
|
+
} finally {
|
|
98
|
+
restore();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('DP-1c: config workflow.text_mode=false wins over CLAUDECODE', () => {
|
|
103
|
+
const restore = _clearClaudeEnv();
|
|
104
|
+
try {
|
|
105
|
+
process.env.CLAUDECODE = '1';
|
|
106
|
+
const sandbox = makeSandbox();
|
|
107
|
+
seedRoadmapYaml(sandbox, _baseRoadmap());
|
|
108
|
+
fs.writeFileSync(
|
|
109
|
+
path.join(sandbox, '.nubos-pilot', 'config.json'),
|
|
110
|
+
JSON.stringify({ workflow: { text_mode: false } }),
|
|
111
|
+
);
|
|
112
|
+
const cap = _captureStdout();
|
|
113
|
+
subcmd.run(['3'], { cwd: sandbox, stdout: cap.stub });
|
|
114
|
+
const payload = JSON.parse(cap.get().trim());
|
|
115
|
+
assert.equal(payload.text_mode, false);
|
|
116
|
+
assert.equal(payload.text_mode_source, 'config');
|
|
117
|
+
} finally {
|
|
118
|
+
restore();
|
|
119
|
+
}
|
|
63
120
|
});
|
|
64
121
|
|
|
65
122
|
test('DP-2: run(["nonexistent"]) throws discuss-invalid-phase-arg', () => {
|
|
@@ -4,6 +4,7 @@ const path = require('node:path');
|
|
|
4
4
|
const { NubosPilotError, atomicWriteFileSync } = require('../../lib/core.cjs');
|
|
5
5
|
const { scan } = require('../../lib/workspace-scan.cjs');
|
|
6
6
|
const { workspaceGitInfo } = require('../../lib/git.cjs');
|
|
7
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
7
8
|
|
|
8
9
|
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
|
|
9
10
|
const PLACEHOLDER_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
@@ -98,6 +99,8 @@ function _emitPlan(projectRoot, flags, stdout) {
|
|
|
98
99
|
|
|
99
100
|
const scanContext = _scanContextFor(projectRoot);
|
|
100
101
|
|
|
102
|
+
const tmDetail = textMode.resolveTextModeDetail(projectRoot);
|
|
103
|
+
|
|
101
104
|
stdout.write(JSON.stringify({
|
|
102
105
|
mode: 'plan',
|
|
103
106
|
sub_mode: mode,
|
|
@@ -107,6 +110,8 @@ function _emitPlan(projectRoot, flags, stdout) {
|
|
|
107
110
|
questions: _grayAreas(),
|
|
108
111
|
required_fields: REQUIRED_FIELDS,
|
|
109
112
|
requirements_md_path: path.join(projectRoot, '.nubos-pilot', 'REQUIREMENTS.md'),
|
|
113
|
+
text_mode: tmDetail.enabled,
|
|
114
|
+
text_mode_source: tmDetail.source,
|
|
110
115
|
}, null, 2));
|
|
111
116
|
}
|
|
112
117
|
|
|
@@ -13,6 +13,7 @@ const layout = require('../../lib/layout.cjs');
|
|
|
13
13
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
14
14
|
const { extractFrontmatter } = require('../../lib/frontmatter.cjs');
|
|
15
15
|
const { getAgentSkills } = require('../../lib/agents.cjs');
|
|
16
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
16
17
|
|
|
17
18
|
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
18
19
|
|
|
@@ -119,6 +120,8 @@ function _initPayload(mNum, cwd) {
|
|
|
119
120
|
tasks,
|
|
120
121
|
});
|
|
121
122
|
}
|
|
123
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
124
|
+
|
|
122
125
|
return {
|
|
123
126
|
_workflow: 'execute-milestone',
|
|
124
127
|
milestone: mNum,
|
|
@@ -132,6 +135,8 @@ function _initPayload(mNum, cwd) {
|
|
|
132
135
|
total_tasks: totalTasks,
|
|
133
136
|
slice_count: slices.length,
|
|
134
137
|
executor_tier: 'sonnet',
|
|
138
|
+
text_mode: tmDetail.enabled,
|
|
139
|
+
text_mode_source: tmDetail.source,
|
|
135
140
|
agent_skills: { executor: _safeSkills('np-executor', cwd) },
|
|
136
141
|
};
|
|
137
142
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const languageMod = require('../../lib/language.cjs');
|
|
4
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
5
|
+
|
|
6
|
+
function _usage() {
|
|
7
|
+
return 'Usage:\n np-tools.cjs lang-directive [--json] [--lang <code>]';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function _emitError(err, stderr) {
|
|
11
|
+
const code = err && err.name === 'NubosPilotError' ? err.code : 'lang-directive-internal-error';
|
|
12
|
+
const message = (err && err.message) || String(err);
|
|
13
|
+
const details = (err && err.details) || null;
|
|
14
|
+
stderr.write(JSON.stringify({ code, message, details }) + '\n');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function run(argv, ctx) {
|
|
18
|
+
const context = ctx || {};
|
|
19
|
+
const cwd = context.cwd || process.cwd();
|
|
20
|
+
const stdout = context.stdout || process.stdout;
|
|
21
|
+
const stderr = context.stderr || process.stderr;
|
|
22
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
23
|
+
|
|
24
|
+
let wantJson = false;
|
|
25
|
+
let override = null;
|
|
26
|
+
for (let i = 0; i < args.length; i++) {
|
|
27
|
+
const a = args[i];
|
|
28
|
+
if (a === '--json') { wantJson = true; continue; }
|
|
29
|
+
if (a === '--lang') { override = args[++i] || null; continue; }
|
|
30
|
+
if (a.startsWith('--lang=')) { override = a.slice('--lang='.length); continue; }
|
|
31
|
+
if (a === '-h' || a === '--help') {
|
|
32
|
+
stdout.write(_usage() + '\n');
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
stderr.write(JSON.stringify({
|
|
36
|
+
code: 'lang-directive-unknown-arg',
|
|
37
|
+
message: 'Unknown argument: ' + a,
|
|
38
|
+
details: { arg: a },
|
|
39
|
+
}) + '\n');
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const language = override != null
|
|
45
|
+
? languageMod.normalizeLanguage(override)
|
|
46
|
+
: languageMod.resolveLanguage(cwd);
|
|
47
|
+
const directive = languageMod.buildDirective(language);
|
|
48
|
+
if (wantJson) {
|
|
49
|
+
stdout.write(JSON.stringify({ language, directive }) + '\n');
|
|
50
|
+
} else {
|
|
51
|
+
stdout.write(directive + '\n');
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
_emitError(err, stderr);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { run };
|
|
61
|
+
|
|
62
|
+
if (require.main === module) {
|
|
63
|
+
process.exit(run(process.argv.slice(2)));
|
|
64
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
|
|
9
|
+
const subcmd = require('./lang-directive.cjs');
|
|
10
|
+
|
|
11
|
+
function _capture() {
|
|
12
|
+
let out = '';
|
|
13
|
+
let err = '';
|
|
14
|
+
const stdout = { write: (s) => { out += s; return true; } };
|
|
15
|
+
const stderr = { write: (s) => { err += s; return true; } };
|
|
16
|
+
return { stdout, stderr, getOut: () => out, getErr: () => err };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _mkProject(language) {
|
|
20
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'np-lang-cli-'));
|
|
21
|
+
fs.mkdirSync(path.join(dir, '.nubos-pilot'), { recursive: true });
|
|
22
|
+
const cfg = language ? { response_language: language } : {};
|
|
23
|
+
fs.writeFileSync(path.join(dir, '.nubos-pilot', 'config.json'), JSON.stringify(cfg));
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('lang-directive: default prints plain directive text', () => {
|
|
28
|
+
const dir = _mkProject('de');
|
|
29
|
+
try {
|
|
30
|
+
const cap = _capture();
|
|
31
|
+
const rc = subcmd.run([], { cwd: dir, stdout: cap.stdout, stderr: cap.stderr });
|
|
32
|
+
assert.equal(rc, 0);
|
|
33
|
+
assert.match(cap.getOut(), /Sprache: \*\*Deutsch\.\*\*/);
|
|
34
|
+
} finally {
|
|
35
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('lang-directive: --json emits language+directive payload', () => {
|
|
40
|
+
const dir = _mkProject('de');
|
|
41
|
+
try {
|
|
42
|
+
const cap = _capture();
|
|
43
|
+
const rc = subcmd.run(['--json'], { cwd: dir, stdout: cap.stdout, stderr: cap.stderr });
|
|
44
|
+
assert.equal(rc, 0);
|
|
45
|
+
const parsed = JSON.parse(cap.getOut());
|
|
46
|
+
assert.equal(parsed.language, 'de');
|
|
47
|
+
assert.match(parsed.directive, /Sprache: \*\*Deutsch\.\*\*/);
|
|
48
|
+
} finally {
|
|
49
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('lang-directive: --lang overrides config', () => {
|
|
54
|
+
const dir = _mkProject('de');
|
|
55
|
+
try {
|
|
56
|
+
const cap = _capture();
|
|
57
|
+
const rc = subcmd.run(['--lang', 'en'], { cwd: dir, stdout: cap.stdout, stderr: cap.stderr });
|
|
58
|
+
assert.equal(rc, 0);
|
|
59
|
+
assert.match(cap.getOut(), /Language: \*\*English\.\*\*/);
|
|
60
|
+
} finally {
|
|
61
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('lang-directive: defaults to en when no config entry', () => {
|
|
66
|
+
const dir = _mkProject(null);
|
|
67
|
+
try {
|
|
68
|
+
const cap = _capture();
|
|
69
|
+
const rc = subcmd.run([], { cwd: dir, stdout: cap.stdout, stderr: cap.stderr });
|
|
70
|
+
assert.equal(rc, 0);
|
|
71
|
+
assert.match(cap.getOut(), /Language: \*\*English\.\*\*/);
|
|
72
|
+
} finally {
|
|
73
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('lang-directive: unknown arg returns error code', () => {
|
|
78
|
+
const dir = _mkProject(null);
|
|
79
|
+
try {
|
|
80
|
+
const cap = _capture();
|
|
81
|
+
const rc = subcmd.run(['--nope'], { cwd: dir, stdout: cap.stdout, stderr: cap.stderr });
|
|
82
|
+
assert.equal(rc, 1);
|
|
83
|
+
const parsed = JSON.parse(cap.getErr().trim());
|
|
84
|
+
assert.equal(parsed.code, 'lang-directive-unknown-arg');
|
|
85
|
+
} finally {
|
|
86
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
const { parseRoadmap } = require('../../lib/roadmap.cjs');
|
|
14
14
|
const { mutateState } = require('../../lib/state.cjs');
|
|
15
15
|
const layout = require('../../lib/layout.cjs');
|
|
16
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
16
17
|
|
|
17
18
|
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'milestone');
|
|
18
19
|
const PLACEHOLDER_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
@@ -49,9 +50,12 @@ function _emit(stdout, payload) {
|
|
|
49
50
|
stdout.write(JSON.stringify(payload, null, 2));
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
function _interviewPayload() {
|
|
53
|
+
function _interviewPayload(cwd) {
|
|
54
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
53
55
|
return {
|
|
54
56
|
mode: 'interview',
|
|
57
|
+
text_mode: tmDetail.enabled,
|
|
58
|
+
text_mode_source: tmDetail.source,
|
|
55
59
|
questions: [
|
|
56
60
|
{ key: 'milestone_name', type: 'input',
|
|
57
61
|
question: 'Milestone name (e.g. "Auth & Basic UI")?' },
|
|
@@ -282,7 +286,7 @@ function run(args, ctx) {
|
|
|
282
286
|
return;
|
|
283
287
|
}
|
|
284
288
|
|
|
285
|
-
_emit(stdout, _interviewPayload());
|
|
289
|
+
_emit(stdout, _interviewPayload(cwd));
|
|
286
290
|
}
|
|
287
291
|
|
|
288
292
|
module.exports = { run, _interviewPayload };
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require('../../lib/core.cjs');
|
|
11
11
|
const { writeState } = require('../../lib/state.cjs');
|
|
12
12
|
const layout = require('../../lib/layout.cjs');
|
|
13
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
13
14
|
|
|
14
15
|
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
|
|
15
16
|
const PLACEHOLDER_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
@@ -42,9 +43,12 @@ function _todayIso() {
|
|
|
42
43
|
return new Date().toISOString().slice(0, 10);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
function _interviewPayload() {
|
|
46
|
+
function _interviewPayload(cwd) {
|
|
47
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
46
48
|
return {
|
|
47
49
|
mode: 'interview',
|
|
50
|
+
text_mode: tmDetail.enabled,
|
|
51
|
+
text_mode_source: tmDetail.source,
|
|
48
52
|
questions: [
|
|
49
53
|
{ key: 'project_name', type: 'input',
|
|
50
54
|
question: 'Project name?' },
|
|
@@ -286,7 +290,7 @@ function run(args, ctx) {
|
|
|
286
290
|
return;
|
|
287
291
|
}
|
|
288
292
|
|
|
289
|
-
_emit(stdout, _interviewPayload());
|
|
293
|
+
_emit(stdout, _interviewPayload(cwd));
|
|
290
294
|
}
|
|
291
295
|
|
|
292
296
|
module.exports = { run, _interviewPayload, _slugify };
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
const layout = require('../../lib/layout.cjs');
|
|
15
15
|
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
16
16
|
const { getAgentSkills } = require('../../lib/agents.cjs');
|
|
17
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
17
18
|
|
|
18
19
|
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
19
20
|
|
|
@@ -104,6 +105,8 @@ function _initPayload(mNum, cwd) {
|
|
|
104
105
|
};
|
|
105
106
|
});
|
|
106
107
|
|
|
108
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
109
|
+
|
|
107
110
|
return {
|
|
108
111
|
_workflow: 'plan-milestone',
|
|
109
112
|
milestone: mNum,
|
|
@@ -122,6 +125,8 @@ function _initPayload(mNum, cwd) {
|
|
|
122
125
|
existing_slices: sliceStatus,
|
|
123
126
|
planner_tier: 'opus',
|
|
124
127
|
checker_tier: 'opus',
|
|
128
|
+
text_mode: tmDetail.enabled,
|
|
129
|
+
text_mode_source: tmDetail.source,
|
|
125
130
|
agent_skills: {
|
|
126
131
|
'np-planner': _safeSkills('np-planner', cwd),
|
|
127
132
|
'np-plan-checker': _safeSkills('np-plan-checker', cwd),
|
|
@@ -7,6 +7,7 @@ const crypto = require('node:crypto');
|
|
|
7
7
|
|
|
8
8
|
const { NubosPilotError, projectStateDir } = require('../../lib/core.cjs');
|
|
9
9
|
const layout = require('../../lib/layout.cjs');
|
|
10
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
10
11
|
|
|
11
12
|
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
12
13
|
|
|
@@ -107,6 +108,8 @@ function run(args, ctx) {
|
|
|
107
108
|
};
|
|
108
109
|
});
|
|
109
110
|
|
|
111
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
112
|
+
|
|
110
113
|
const payload = {
|
|
111
114
|
_workflow: 'research-phase',
|
|
112
115
|
milestone: mNum,
|
|
@@ -118,6 +121,8 @@ function run(args, ctx) {
|
|
|
118
121
|
has_research,
|
|
119
122
|
slice_research: sliceResearch,
|
|
120
123
|
tools_available: _toolsAvailable(),
|
|
124
|
+
text_mode: tmDetail.enabled,
|
|
125
|
+
text_mode_source: tmDetail.source,
|
|
121
126
|
agent_skills: _agentSkills(cwd),
|
|
122
127
|
};
|
|
123
128
|
_emit(payload, stdout, cwd);
|
|
@@ -4,6 +4,7 @@ const { NubosPilotError } = require('../../lib/core.cjs');
|
|
|
4
4
|
const { readState } = require('../../lib/state.cjs');
|
|
5
5
|
const { readCheckpoint, listCheckpoints } = require('../../lib/checkpoint.cjs');
|
|
6
6
|
const { TASK_ID_RE } = require('../../lib/tasks.cjs');
|
|
7
|
+
const textMode = require('../../lib/text-mode.cjs');
|
|
7
8
|
|
|
8
9
|
function _safeReadState(cwd) {
|
|
9
10
|
try { return readState(cwd); } catch { return null; }
|
|
@@ -69,6 +70,10 @@ function run(_args, ctx) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
74
|
+
payload.text_mode = tmDetail.enabled;
|
|
75
|
+
payload.text_mode_source = tmDetail.source;
|
|
76
|
+
|
|
72
77
|
stdout.write(JSON.stringify(payload));
|
|
73
78
|
return payload;
|
|
74
79
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveTextModeDetail } = require('../../lib/text-mode.cjs');
|
|
4
|
+
|
|
5
|
+
function _usage() {
|
|
6
|
+
return 'Usage:\n np-tools.cjs text-mode [--json]';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function _emitError(err, stderr) {
|
|
10
|
+
const code = err && err.name === 'NubosPilotError' ? err.code : 'text-mode-internal-error';
|
|
11
|
+
const message = (err && err.message) || String(err);
|
|
12
|
+
const details = (err && err.details) || null;
|
|
13
|
+
stderr.write(JSON.stringify({ code, message, details }) + '\n');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function run(argv, ctx) {
|
|
17
|
+
const context = ctx || {};
|
|
18
|
+
const cwd = context.cwd || process.cwd();
|
|
19
|
+
const stdout = context.stdout || process.stdout;
|
|
20
|
+
const stderr = context.stderr || process.stderr;
|
|
21
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
22
|
+
|
|
23
|
+
let wantJson = false;
|
|
24
|
+
for (const a of args) {
|
|
25
|
+
if (a === '--json') { wantJson = true; continue; }
|
|
26
|
+
if (a === '-h' || a === '--help') {
|
|
27
|
+
stdout.write(_usage() + '\n');
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
stderr.write(JSON.stringify({
|
|
31
|
+
code: 'text-mode-unknown-arg',
|
|
32
|
+
message: 'Unknown argument: ' + a,
|
|
33
|
+
details: { arg: a },
|
|
34
|
+
}) + '\n');
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const detail = resolveTextModeDetail(cwd);
|
|
40
|
+
if (wantJson) {
|
|
41
|
+
stdout.write(JSON.stringify(detail) + '\n');
|
|
42
|
+
} else {
|
|
43
|
+
stdout.write(String(detail.enabled) + '\n');
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
_emitError(err, stderr);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { run };
|
|
53
|
+
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
process.exit(run(process.argv.slice(2)));
|
|
56
|
+
}
|