nubos-pilot 0.5.1 → 0.5.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/bin/install.js +7 -15
- package/bin/np-tools/_commands.cjs +1 -0
- package/bin/np-tools/lang-directive.cjs +64 -0
- package/bin/np-tools/lang-directive.test.cjs +88 -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 +13 -1
- package/lib/runtime/claude.test.cjs +35 -0
- package/np-tools.cjs +1 -0
- package/package.json +1 -1
- package/workflows/discuss-phase.md +17 -5
- package/workflows/discuss-project.md +6 -0
- package/workflows/execute-phase.md +9 -1
- package/workflows/new-milestone.md +6 -0
- package/workflows/new-project.md +7 -0
- package/workflows/pause-work.md +5 -0
- package/workflows/plan-phase.md +7 -0
- package/workflows/research-phase.md +7 -0
- package/workflows/resume-work.md +7 -0
- package/workflows/scan-codebase.md +8 -0
- package/workflows/session-report.md +7 -0
- package/workflows/update-docs.md +7 -0
- package/workflows/validate-phase.md +7 -0
- package/workflows/verify-work.md +8 -0
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
|
}
|
|
@@ -166,7 +156,7 @@ function _writeToolsShim(projectRoot) {
|
|
|
166
156
|
+ ' process.stderr.write("nubos-pilot: tool binary fehlt unter " + TARGET + "\\nFix: npx nubos-pilot@latest update\\n");\n'
|
|
167
157
|
+ ' process.exit(1);\n'
|
|
168
158
|
+ '}\n'
|
|
169
|
-
+ 'require(TARGET);\n';
|
|
159
|
+
+ 'require(TARGET).main();\n';
|
|
170
160
|
fs.mkdirSync(shimDir, { recursive: true });
|
|
171
161
|
atomicWriteFileSync(shimPath, body);
|
|
172
162
|
try { fs.chmodSync(shimPath, 0o755); } catch {}
|
|
@@ -681,8 +671,10 @@ async function main() {
|
|
|
681
671
|
return await runInstall({ cwd, mode: detectMode(cwd), flags });
|
|
682
672
|
case '--dry-run':
|
|
683
673
|
return await runInstall({ cwd, mode: detectMode(cwd), dryRun: true, flags });
|
|
684
|
-
case 'update':
|
|
685
|
-
|
|
674
|
+
case 'update': {
|
|
675
|
+
const detected = detectMode(cwd);
|
|
676
|
+
return await runInstall({ cwd, mode: detected === 'init' ? 'init' : 'update', flags });
|
|
677
|
+
}
|
|
686
678
|
case 'uninstall':
|
|
687
679
|
return await runUninstall({ cwd, args: rest.slice(1) });
|
|
688
680
|
case 'doctor': {
|
|
@@ -43,6 +43,7 @@ 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)' },
|
|
46
47
|
{ name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify' },
|
|
47
48
|
{ name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
|
|
48
49
|
|
|
@@ -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
|
+
});
|
package/lib/language.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { findProjectRoot, NubosPilotError } = require('./core.cjs');
|
|
6
|
+
|
|
7
|
+
const LANG_DIRECTIVES = {
|
|
8
|
+
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.',
|
|
9
|
+
en: 'Language: **English.** All `/np:*` slash-command output, askuser prompts and status updates respond in English.',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const DEFAULT_LANGUAGE = 'en';
|
|
13
|
+
|
|
14
|
+
function normalizeLanguage(raw) {
|
|
15
|
+
const s = String(raw || '').trim().toLowerCase();
|
|
16
|
+
return s || DEFAULT_LANGUAGE;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function buildDirective(language) {
|
|
20
|
+
const lang = normalizeLanguage(language);
|
|
21
|
+
if (LANG_DIRECTIVES[lang]) return LANG_DIRECTIVES[lang];
|
|
22
|
+
return 'Language: respond in the ISO-639 language `' + lang + '` for all `/np:*` slash-command output, askuser prompts and status updates.';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readConfigLanguage(cwd) {
|
|
26
|
+
let root;
|
|
27
|
+
try {
|
|
28
|
+
root = findProjectRoot(cwd || process.cwd());
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err && err.code === 'not-in-project') return null;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
const p = path.join(root, '.nubos-pilot', 'config.json');
|
|
34
|
+
if (!fs.existsSync(p)) return null;
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw new NubosPilotError('language-config-parse-error', 'config.json invalid JSON', { cause: err && err.message });
|
|
40
|
+
}
|
|
41
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
42
|
+
const raw = parsed.response_language;
|
|
43
|
+
if (raw == null || raw === '') return null;
|
|
44
|
+
return normalizeLanguage(raw);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveLanguage(cwd) {
|
|
48
|
+
return readConfigLanguage(cwd) || DEFAULT_LANGUAGE;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveDirective(cwd) {
|
|
52
|
+
return buildDirective(resolveLanguage(cwd));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
LANG_DIRECTIVES,
|
|
57
|
+
DEFAULT_LANGUAGE,
|
|
58
|
+
normalizeLanguage,
|
|
59
|
+
buildDirective,
|
|
60
|
+
readConfigLanguage,
|
|
61
|
+
resolveLanguage,
|
|
62
|
+
resolveDirective,
|
|
63
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
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 lang = require('./language.cjs');
|
|
10
|
+
|
|
11
|
+
function _mkSandbox() {
|
|
12
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'np-lang-'));
|
|
13
|
+
fs.mkdirSync(path.join(dir, '.nubos-pilot'), { recursive: true });
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function _writeConfig(dir, obj) {
|
|
18
|
+
fs.writeFileSync(path.join(dir, '.nubos-pilot', 'config.json'), JSON.stringify(obj, null, 2));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('language: normalizeLanguage lowercases and trims', () => {
|
|
22
|
+
assert.equal(lang.normalizeLanguage('DE'), 'de');
|
|
23
|
+
assert.equal(lang.normalizeLanguage(' En '), 'en');
|
|
24
|
+
assert.equal(lang.normalizeLanguage(''), 'en');
|
|
25
|
+
assert.equal(lang.normalizeLanguage(null), 'en');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('language: buildDirective returns known de/en strings', () => {
|
|
29
|
+
const de = lang.buildDirective('de');
|
|
30
|
+
assert.match(de, /Sprache: \*\*Deutsch\.\*\*/);
|
|
31
|
+
const en = lang.buildDirective('en');
|
|
32
|
+
assert.match(en, /Language: \*\*English\.\*\*/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('language: buildDirective falls back to ISO-639 template for unknown code', () => {
|
|
36
|
+
const fr = lang.buildDirective('fr');
|
|
37
|
+
assert.match(fr, /ISO-639 language `fr`/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('language: readConfigLanguage returns null when no config', () => {
|
|
41
|
+
const dir = _mkSandbox();
|
|
42
|
+
try {
|
|
43
|
+
assert.equal(lang.readConfigLanguage(dir), null);
|
|
44
|
+
} finally {
|
|
45
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('language: readConfigLanguage returns normalized value from config', () => {
|
|
50
|
+
const dir = _mkSandbox();
|
|
51
|
+
try {
|
|
52
|
+
_writeConfig(dir, { response_language: 'DE' });
|
|
53
|
+
assert.equal(lang.readConfigLanguage(dir), 'de');
|
|
54
|
+
} finally {
|
|
55
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('language: readConfigLanguage returns null when response_language absent', () => {
|
|
60
|
+
const dir = _mkSandbox();
|
|
61
|
+
try {
|
|
62
|
+
_writeConfig(dir, { runtime: 'claude' });
|
|
63
|
+
assert.equal(lang.readConfigLanguage(dir), null);
|
|
64
|
+
} finally {
|
|
65
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('language: resolveLanguage defaults to en when no project root', () => {
|
|
70
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'np-outside-'));
|
|
71
|
+
try {
|
|
72
|
+
assert.equal(lang.resolveLanguage(outside), 'en');
|
|
73
|
+
} finally {
|
|
74
|
+
fs.rmSync(outside, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('language: resolveDirective uses config language', () => {
|
|
79
|
+
const dir = _mkSandbox();
|
|
80
|
+
try {
|
|
81
|
+
_writeConfig(dir, { response_language: 'de' });
|
|
82
|
+
assert.match(lang.resolveDirective(dir), /Sprache: \*\*Deutsch\.\*\*/);
|
|
83
|
+
} finally {
|
|
84
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('language: readConfigLanguage throws on invalid JSON', () => {
|
|
89
|
+
const dir = _mkSandbox();
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(path.join(dir, '.nubos-pilot', 'config.json'), '{not json');
|
|
92
|
+
assert.throws(
|
|
93
|
+
() => lang.readConfigLanguage(dir),
|
|
94
|
+
(err) => err && err.code === 'language-config-parse-error',
|
|
95
|
+
);
|
|
96
|
+
} finally {
|
|
97
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
@@ -7,6 +7,10 @@ function _setReadlineImplForTests(impl) {
|
|
|
7
7
|
_readlineImpl = impl || null;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function _hasReadlineImplForTests() {
|
|
11
|
+
return _readlineImpl != null;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
function _readOneLine() {
|
|
11
15
|
if (_readlineImpl) return Promise.resolve(_readlineImpl());
|
|
12
16
|
return new Promise((resolve, reject) => {
|
|
@@ -156,4 +160,4 @@ async function askUserReadline({ type, question, options, def }) {
|
|
|
156
160
|
return { value: _parseAnswer(type, line, options, def), source: 'readline' };
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
module.exports = { askUserReadline, _readOneLine, _parseAnswer, _setReadlineImplForTests };
|
|
163
|
+
module.exports = { askUserReadline, _readOneLine, _parseAnswer, _setReadlineImplForTests, _hasReadlineImplForTests };
|
|
@@ -115,9 +115,10 @@ test('RL-9: _parseAnswer unknown type throws askuser-invalid-type', () => {
|
|
|
115
115
|
);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
test('RL-10: module exports
|
|
118
|
+
test('RL-10: module exports readline helpers + claude-runtime TTY probe', () => {
|
|
119
119
|
const keys = Object.keys(rl).sort();
|
|
120
120
|
assert.deepEqual(keys, [
|
|
121
|
+
'_hasReadlineImplForTests',
|
|
121
122
|
'_parseAnswer',
|
|
122
123
|
'_readOneLine',
|
|
123
124
|
'_setReadlineImplForTests',
|
package/lib/runtime/claude.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const { _readOneLine, _parseAnswer } = require('./_readline.cjs');
|
|
1
|
+
const { _readOneLine, _parseAnswer, _hasReadlineImplForTests } = require('./_readline.cjs');
|
|
2
|
+
const { NubosPilotError } = require('../core.cjs');
|
|
2
3
|
|
|
3
4
|
function _emitClaudeMarkerBlock({ type, question, options, def }) {
|
|
4
5
|
const payload = {
|
|
@@ -17,6 +18,17 @@ async function askUser(spec) {
|
|
|
17
18
|
const question = spec && spec.question;
|
|
18
19
|
const options = spec && spec.options;
|
|
19
20
|
const def = spec ? spec.default : undefined;
|
|
21
|
+
const hasTTY = !!process.stdin.isTTY;
|
|
22
|
+
if (!hasTTY && !_hasReadlineImplForTests()) {
|
|
23
|
+
if (def !== undefined && def !== null) {
|
|
24
|
+
return { value: def, source: 'default' };
|
|
25
|
+
}
|
|
26
|
+
throw new NubosPilotError(
|
|
27
|
+
'askuser-no-tty',
|
|
28
|
+
'askUser cannot prompt without TTY (Claude Code Bash has no interactive stdin). Fall back to plain-text numbered list.',
|
|
29
|
+
{ question, type },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
20
32
|
const line = await _emitClaudeMarkerBlock({ type, question, options, def });
|
|
21
33
|
return { value: _parseAnswer(type, line, options, def), source: 'askUserQuestion' };
|
|
22
34
|
}
|
|
@@ -99,3 +99,38 @@ test('claude-adapter: exports runtimeNotice compatible with agents-md SC-5 check
|
|
|
99
99
|
test('claude-adapter: runtimeNotice does not contain the forbidden joined Claude-tool literal (SC-5 guard)', () => {
|
|
100
100
|
assert.ok(!/Ask-User-Question/.test(claude.runtimeNotice));
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
test('claude-adapter: askUser without TTY and without default throws askuser-no-tty', async () => {
|
|
104
|
+
const originalIsTTY = process.stdin.isTTY;
|
|
105
|
+
try {
|
|
106
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
|
|
107
|
+
_setReadlineImplForTests(null);
|
|
108
|
+
await assert.rejects(
|
|
109
|
+
() => claude.askUser({ type: 'select', question: 'P', options: ['A', 'B'] }),
|
|
110
|
+
(err) => err && err.code === 'askuser-no-tty',
|
|
111
|
+
);
|
|
112
|
+
} finally {
|
|
113
|
+
if (originalIsTTY === undefined) {
|
|
114
|
+
delete process.stdin.isTTY;
|
|
115
|
+
} else {
|
|
116
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('claude-adapter: askUser without TTY but with default returns default', async () => {
|
|
122
|
+
const originalIsTTY = process.stdin.isTTY;
|
|
123
|
+
try {
|
|
124
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
|
|
125
|
+
_setReadlineImplForTests(null);
|
|
126
|
+
const res = await claude.askUser({ type: 'confirm', question: 'OK?', default: true });
|
|
127
|
+
assert.equal(res.value, true);
|
|
128
|
+
assert.equal(res.source, 'default');
|
|
129
|
+
} finally {
|
|
130
|
+
if (originalIsTTY === undefined) {
|
|
131
|
+
delete process.stdin.isTTY;
|
|
132
|
+
} else {
|
|
133
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
package/np-tools.cjs
CHANGED
|
@@ -45,6 +45,7 @@ const topLevelCommands = {
|
|
|
45
45
|
'metrics': require('./bin/np-tools/metrics.cjs'),
|
|
46
46
|
'resolve-model': require('./bin/np-tools/resolve-model.cjs'),
|
|
47
47
|
'stats': require('./bin/np-tools/stats.cjs'),
|
|
48
|
+
'lang-directive': require('./bin/np-tools/lang-directive.cjs'),
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
const THRESHOLD = 16 * 1024;
|
package/package.json
CHANGED
|
@@ -19,10 +19,17 @@ workflow delivers PLAN-01 and nothing beyond it.
|
|
|
19
19
|
## Initialize
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
22
23
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init discuss-phase "$PHASE")
|
|
23
24
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
28
|
+
`$LANG_DIRECTIVE` is authoritative for this workflow. Obey it for ALL
|
|
29
|
+
subsequent output, askuser prompt texts, status updates, and the CONTEXT.md
|
|
30
|
+
rendering. This supersedes any directive in CLAUDE.md managed block if they
|
|
31
|
+
conflict — the config is the single source of truth.
|
|
32
|
+
|
|
26
33
|
Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`,
|
|
27
34
|
`milestone_context_path`, `has_context`, `has_milestone_dir`, `goal`,
|
|
28
35
|
`requirements`, `agent_skills`, `mode`.
|
|
@@ -118,11 +125,16 @@ Capture the idea in a "Deferred Ideas" section. Don't lose it, don't act on it.
|
|
|
118
125
|
## Answer Validation
|
|
119
126
|
|
|
120
127
|
<answer_validation>
|
|
121
|
-
**IMPORTANT: Answer validation** — After every interactive prompt, check
|
|
122
|
-
|
|
123
|
-
1.
|
|
124
|
-
|
|
125
|
-
the
|
|
128
|
+
**IMPORTANT: Answer validation** — After every interactive prompt, check the
|
|
129
|
+
exit code and the response:
|
|
130
|
+
1. If `askuser` exits with structured error `askuser-no-tty` (exit code 1,
|
|
131
|
+
stderr JSON with `"code":"askuser-no-tty"`), **skip retry** and fall back
|
|
132
|
+
immediately to the plain-text numbered list described below — the runtime
|
|
133
|
+
cannot prompt interactively in this session.
|
|
134
|
+
2. If the response is empty or whitespace-only (exit 0 but no value), retry
|
|
135
|
+
the question once with the same parameters.
|
|
136
|
+
3. If still empty, present the options as a plain-text numbered list and ask
|
|
137
|
+
the user to type their choice number.
|
|
126
138
|
Never proceed with an empty answer.
|
|
127
139
|
|
|
128
140
|
**Text mode (`workflow.text_mode: true` in config or `--text` flag):**
|
|
@@ -51,10 +51,16 @@ Never:
|
|
|
51
51
|
## Single-Call Init
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
54
55
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init discuss-project ${BOOTSTRAP:+--bootstrap})
|
|
55
56
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
60
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for all askuser prompt texts,
|
|
61
|
+
narrative status updates, and the prose written into PROJECT.md sections.
|
|
62
|
+
Supersedes CLAUDE.md managed block.
|
|
63
|
+
|
|
58
64
|
Parse: `mode`, `sub_mode` (`bootstrap` or `refresh`), `project_md_exists`,
|
|
59
65
|
`scan_context`, `questions[]`, `required_fields[]`.
|
|
60
66
|
|
|
@@ -16,12 +16,20 @@ Execute every slice of a milestone in wave order: slice S001 first (all its task
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
PHASE="$1"
|
|
19
|
-
|
|
19
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
20
|
+
INIT=$(node .nubos-pilot/bin/np-tools.cjs init execute-milestone init "$PHASE")
|
|
20
21
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
21
22
|
AGENT_SKILLS_EXECUTOR=$(node .nubos-pilot/bin/np-tools.cjs agent-skills executor 2>/dev/null)
|
|
22
23
|
RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
|
|
23
24
|
```
|
|
24
25
|
|
|
26
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
27
|
+
`$LANG_DIRECTIVE` is authoritative for this workflow. Obey it for all user-
|
|
28
|
+
facing output, askuser prompts, and status updates. Pass `$LANG_DIRECTIVE`
|
|
29
|
+
into every np-executor spawn prompt as a system-level rule so task summaries
|
|
30
|
+
and checkpoint notes follow the project language. This supersedes any
|
|
31
|
+
directive in CLAUDE.md managed block.
|
|
32
|
+
|
|
25
33
|
Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `waves[]` (each with `wave` (= slice number), `slice_id`, `slice_full_id`, `slice_dir`, `tasks[]`), `total_tasks`, `slice_count`, `executor_tier`, `agent_skills`.
|
|
26
34
|
|
|
27
35
|
`PLAN_ID` is iterated per slice as `${milestone_id}-${slice_id}` (e.g. `M001-S001`). `TASK_ID` is iterated from each slice's `tasks[]` (e.g. `M001-S001-T0001`).
|
|
@@ -52,10 +52,16 @@ The subcommand raises `project-not-initialized` anyway, but the shell check give
|
|
|
52
52
|
## Single-Call Init
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
55
56
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init new-milestone)
|
|
56
57
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
61
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for askuser prompt texts,
|
|
62
|
+
user-facing output, and any prose written into milestone artefacts (YAML
|
|
63
|
+
keys, IDs, and identifiers stay canonical English). Supersedes CLAUDE.md.
|
|
64
|
+
|
|
59
65
|
Payload: three questions — `milestone_name`, `milestone_goal`, `create_req_prefix` (confirm).
|
|
60
66
|
|
|
61
67
|
## Interview
|
package/workflows/new-project.md
CHANGED
|
@@ -106,10 +106,17 @@ Use the scan to propose:
|
|
|
106
106
|
The 5 structural questions. All prompts go through the askuser gateway.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
109
110
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init new-project)
|
|
110
111
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
115
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for all askuser prompt texts,
|
|
116
|
+
user-facing output, and any narrative prose written into PROJECT.md /
|
|
117
|
+
REQUIREMENTS.md (field names and YAML keys stay canonical English).
|
|
118
|
+
Supersedes CLAUDE.md.
|
|
119
|
+
|
|
113
120
|
```bash
|
|
114
121
|
ANS_PROJECT_NAME=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{"type":"input","prompt":"Project name?"}')
|
|
115
122
|
ANS_CORE_VALUE=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{"type":"input","prompt":"Core value — one sentence that must stay true if everything else fails?"}')
|
package/workflows/pause-work.md
CHANGED
|
@@ -15,9 +15,14 @@ progress.
|
|
|
15
15
|
## Execution
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
18
19
|
node .nubos-pilot/bin/np-tools.cjs init pause-work
|
|
19
20
|
```
|
|
20
21
|
|
|
22
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
23
|
+
Obey `$LANG_DIRECTIVE` for the resume-hint narration and any status lines
|
|
24
|
+
printed around the JSON payload. Supersedes CLAUDE.md.
|
|
25
|
+
|
|
21
26
|
Output is a small JSON payload `{ ok, stopped_at, resume_file }`. The
|
|
22
27
|
workflow simply displays it.
|
|
23
28
|
|
package/workflows/plan-phase.md
CHANGED
|
@@ -72,6 +72,7 @@ fi
|
|
|
72
72
|
### Read milestone state
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
75
76
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init plan-milestone init "$PHASE")
|
|
76
77
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
77
78
|
AGENT_SKILLS_PLANNER=$(node .nubos-pilot/bin/np-tools.cjs agent-skills planner 2>/dev/null)
|
|
@@ -79,6 +80,12 @@ AGENT_SKILLS_CHECKER=$(node .nubos-pilot/bin/np-tools.cjs agent-skills plan-chec
|
|
|
79
80
|
RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
84
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for all user-facing output,
|
|
85
|
+
askuser prompts, status updates, and any narrative text the spawned planner
|
|
86
|
+
or plan-checker subagents emit. Pass `$LANG_DIRECTIVE` into their spawn
|
|
87
|
+
prompts as a system-level rule. This supersedes any directive in CLAUDE.md.
|
|
88
|
+
|
|
82
89
|
Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `milestone_context_path`, `milestone_roadmap_path`, `milestone_meta_path`, `name`, `goal`, `requirements`, `success_criteria`, `has_context`, `has_roadmap`, `has_meta`, `existing_slices[]`, `planner_tier`, `checker_tier`, `agent_skills`.
|
|
83
90
|
|
|
84
91
|
`PLAN_ID` and `TASK_ID` default to `${milestone_id}-plan` / `${milestone_id}-planner-run` for the metrics records.
|
|
@@ -92,11 +92,18 @@ payload; larger payloads are written to a tmp file and referenced via
|
|
|
92
92
|
`@file:<path>`.
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
95
96
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init research-phase "$PHASE")
|
|
96
97
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
97
98
|
RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
|
|
98
99
|
```
|
|
99
100
|
|
|
101
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
102
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output and
|
|
103
|
+
askuser prompts, and pass it into the np-researcher spawn prompt so
|
|
104
|
+
RESEARCH.md prose (not URLs, citations, or code snippets) follows the
|
|
105
|
+
project language. This supersedes CLAUDE.md.
|
|
106
|
+
|
|
100
107
|
`RUNTIME` is resolved once here and reused by the metrics-record call at the
|
|
101
108
|
researcher spawn site (Step 4) per D-06 workflow-writer pattern.
|
|
102
109
|
|
package/workflows/resume-work.md
CHANGED
|
@@ -13,10 +13,17 @@ on each accordingly.
|
|
|
13
13
|
## Initialize
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
16
17
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init resume-work)
|
|
17
18
|
STATUS=$(echo "$INIT" | node -e "process.stdin.on('data', d => console.log(JSON.parse(d).status))")
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
22
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output and
|
|
23
|
+
askuser prompts. When spawning the np-executor to continue a checkpoint,
|
|
24
|
+
pass `$LANG_DIRECTIVE` into the spawn prompt so resumed task summaries
|
|
25
|
+
follow the project language. Supersedes CLAUDE.md.
|
|
26
|
+
|
|
20
27
|
## Execution
|
|
21
28
|
|
|
22
29
|
### status: resume
|
|
@@ -60,10 +60,18 @@ end.
|
|
|
60
60
|
Scan, group, write manifest + stubs, emit module-facts in one call:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
63
64
|
PLAN=$(node .nubos-pilot/bin/np-tools.cjs scan-codebase --project-name "$PROJECT_NAME")
|
|
64
65
|
if [[ "$PLAN" == @file:* ]]; then PLAN=$(cat "${PLAN#@file:}"); fi
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
69
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output and
|
|
70
|
+
askuser prompts. Pass it into any writer-subagent spawn so module-doc
|
|
71
|
+
prose (overview, responsibilities, notes) follows the project language.
|
|
72
|
+
Module IDs, file paths, symbol names, and language labels stay canonical
|
|
73
|
+
English. Supersedes CLAUDE.md.
|
|
74
|
+
|
|
67
75
|
`--project-name` is optional; when provided it goes into `INDEX.md`. Other
|
|
68
76
|
flags: `--batch-size N` (default 500), `--max-files N`.
|
|
69
77
|
|
|
@@ -32,6 +32,7 @@ record. Pitfall 9 / `workflow-missing-metrics` is exempt.
|
|
|
32
32
|
## Initialize
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
35
36
|
SINCE_OVERRIDE=""
|
|
36
37
|
for arg in "$@"; do
|
|
37
38
|
case "$arg" in
|
|
@@ -49,6 +50,12 @@ LOCAL_FILENAME_TS=$(date +"%Y-%m-%dT%H%M")
|
|
|
49
50
|
REPORT_PATH="${REPORTS_DIR}/${LOCAL_FILENAME_TS}-session-report.md"
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
54
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for the report's narrative
|
|
55
|
+
sections (summary, highlights, notable events) and any askuser prompts.
|
|
56
|
+
Task IDs, milestone IDs, commit SHAs, metrics keys, and file paths stay
|
|
57
|
+
canonical English. Supersedes CLAUDE.md.
|
|
58
|
+
|
|
52
59
|
The filename format is `YYYY-MM-DDTHHMM-session-report.md` (D-17 —
|
|
53
60
|
4-char HHMM, no seconds, local time) so reports sort
|
|
54
61
|
lexicographically by invocation order.
|
package/workflows/update-docs.md
CHANGED
|
@@ -51,10 +51,17 @@ If `.doc-index.json` is missing, fall back to a full rescan via
|
|
|
51
51
|
## Single-Call Init
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
54
55
|
PLAN=$(node .nubos-pilot/bin/np-tools.cjs update-docs)
|
|
55
56
|
if [[ "$PLAN" == @file:* ]]; then PLAN=$(cat "${PLAN#@file:}"); fi
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
60
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output and
|
|
61
|
+
askuser prompts. Pass it into any writer-subagent spawn so refreshed
|
|
62
|
+
module-doc prose follows the project language. Module IDs, file paths,
|
|
63
|
+
and symbol names stay canonical English. Supersedes CLAUDE.md.
|
|
64
|
+
|
|
58
65
|
Parse: `mode`, `diff_summary` (added/removed/changed/unchanged counts),
|
|
59
66
|
`stale_modules[]`, `added_modules[]`, `removed_modules[]`.
|
|
60
67
|
|
|
@@ -19,11 +19,18 @@ if [[ -z "$PHASE" ]]; then
|
|
|
19
19
|
exit 2
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
22
23
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init verify-work "$PHASE")
|
|
23
24
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
24
25
|
RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
|
|
25
26
|
```
|
|
26
27
|
|
|
28
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
29
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output, askuser
|
|
30
|
+
prompts, and pass it into the np-nyquist-auditor spawn prompt so gap-fill
|
|
31
|
+
narrative follows the project language. Test IDs, file paths, and canonical
|
|
32
|
+
field names stay English. Supersedes CLAUDE.md.
|
|
33
|
+
|
|
27
34
|
Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`, `slice_uat`.
|
|
28
35
|
|
|
29
36
|
```bash
|
package/workflows/verify-work.md
CHANGED
|
@@ -16,11 +16,19 @@ Slice-level acceptance (UAT) is validated separately by `/np:validate-phase <N>`
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
PHASE="$1"
|
|
19
|
+
LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
|
|
19
20
|
INIT=$(node .nubos-pilot/bin/np-tools.cjs init verify-work "$PHASE")
|
|
20
21
|
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
21
22
|
AGENT_SKILLS_VERIFIER=$(node .nubos-pilot/bin/np-tools.cjs agent-skills verifier 2>/dev/null)
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
26
|
+
`$LANG_DIRECTIVE` is authoritative. Obey it for user-facing output, askuser
|
|
27
|
+
prompts, and pass it into the np-verifier spawn prompt so VERIFICATION.md
|
|
28
|
+
prose (Pass/Fail findings, root-cause notes) follows the project language.
|
|
29
|
+
Test-case IDs, file paths, and stack traces stay canonical. Supersedes
|
|
30
|
+
CLAUDE.md.
|
|
31
|
+
|
|
24
32
|
Parse: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`, `success_criteria`, `draft_results`, `verification_path`, `slice_uat`, `verifier_tier`, `agent_skills`.
|
|
25
33
|
|
|
26
34
|
## Pass 1 — verifier agent
|