nubos-pilot 0.5.8 → 0.5.9
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 +3 -7
- package/bin/np-tools/_commands.cjs +1 -0
- package/bin/np-tools/commit.cjs +32 -14
- package/bin/np-tools/commit.test.cjs +30 -3
- package/bin/np-tools/template-path.cjs +61 -0
- package/bin/np-tools/template-path.test.cjs +86 -0
- package/lib/config-defaults.cjs +40 -0
- package/lib/git.cjs +11 -7
- package/np-tools.cjs +1 -0
- package/package.json +1 -1
- package/workflows/discuss-phase.md +4 -3
- package/workflows/validate-phase.md +4 -4
package/bin/install.js
CHANGED
|
@@ -17,6 +17,7 @@ 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
19
|
const languageMod = require('../lib/language.cjs');
|
|
20
|
+
const configDefaults = require('../lib/config-defaults.cjs');
|
|
20
21
|
|
|
21
22
|
const cyan = '\x1b[36m', green = '\x1b[32m', yellow = '\x1b[33m',
|
|
22
23
|
red = '\x1b[31m', blue = '\x1b[38;5;33m',
|
|
@@ -248,16 +249,11 @@ async function _runInitQuestions(detectedRuntime, askUser, flags) {
|
|
|
248
249
|
const model_profile = (await askUser({ type: 'select', question: 'Model-Profile?',
|
|
249
250
|
options: ['frontier', 'quality', 'balanced', 'budget', 'inherit'], default: 'frontier' })).value;
|
|
250
251
|
const response_language = (await askUser({ type: 'input', question: 'Response language (ISO-639 code)?', default: 'en' })).value;
|
|
251
|
-
return {
|
|
252
|
+
return configDefaults.buildInstallConfig({
|
|
252
253
|
runtime, runtimes, scope, mcp: !!f.mcp,
|
|
253
254
|
model_profile,
|
|
254
255
|
response_language,
|
|
255
|
-
|
|
256
|
-
parallelization: true,
|
|
257
|
-
research: true,
|
|
258
|
-
plan_checker: true,
|
|
259
|
-
verifier: true,
|
|
260
|
-
};
|
|
256
|
+
});
|
|
261
257
|
}
|
|
262
258
|
|
|
263
259
|
function _repairCodexConfig() {
|
|
@@ -48,6 +48,7 @@ const COMMANDS = [
|
|
|
48
48
|
{ name: 'generate-slug', category: 'Utility', description: 'Slugify text via lib/layout.cjs.slugify' },
|
|
49
49
|
{ name: 'stats', category: 'Utility', description: 'Aggregated project stats (roadmap + STATE + git + metrics JSON shape)' },
|
|
50
50
|
{ name: 'detect-runtime', category: 'Utility', description: 'Print detected runtime id (claude, codex, gemini, …) — reads config.json ∨ env ∨ default' },
|
|
51
|
+
{ name: 'template-path', category: 'Utility', description: 'Print absolute path to a package-shipped template by name (e.g. VALIDATION, milestone/CONTEXT)' },
|
|
51
52
|
|
|
52
53
|
{ name: 'thread', category: 'Utility', description: 'Cross-session thread CRUD (create/resume under .nubos-pilot/threads/)' },
|
|
53
54
|
{ name: 'session-report', category: 'Utility', description: 'Generate session report from metrics since .last-session pointer' },
|
package/bin/np-tools/commit.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { execFileSync } = require('node:child_process');
|
|
2
|
+
const fs = require('node:fs');
|
|
2
3
|
const path = require('node:path');
|
|
3
|
-
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { NubosPilotError, findProjectRoot } = require('../../lib/core.cjs');
|
|
4
5
|
const { assertCommittablePaths } = require('../../lib/git.cjs');
|
|
5
6
|
const { resolveCommitArtifacts } = require('../../lib/commit-policy.cjs');
|
|
6
7
|
|
|
@@ -47,19 +48,34 @@ function _parseArgs(argv) {
|
|
|
47
48
|
return { msg, files };
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
function
|
|
51
|
-
|
|
51
|
+
function _realpathOrResolve(p) {
|
|
52
|
+
try { return fs.realpathSync(p); } catch { return path.resolve(p); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _normalizeFiles(files, cwd, root) {
|
|
56
|
+
const realRoot = _realpathOrResolve(root);
|
|
57
|
+
return files.map((f) => {
|
|
52
58
|
if (typeof f !== 'string' || f.length === 0) {
|
|
53
59
|
throw new NubosPilotError('commit-invalid-path', 'commit path must be non-empty string', { path: f });
|
|
54
60
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const abs = path.isAbsolute(f) ? path.resolve(f) : path.resolve(cwd, f);
|
|
62
|
+
const realAbs = _realpathOrResolve(abs);
|
|
63
|
+
const rel = path.relative(realRoot, realAbs);
|
|
64
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
65
|
+
throw new NubosPilotError(
|
|
66
|
+
'commit-path-outside-project',
|
|
67
|
+
'commit path must resolve inside project root',
|
|
68
|
+
{ path: f, root },
|
|
69
|
+
);
|
|
60
70
|
}
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
return rel;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _validateFiles(files) {
|
|
76
|
+
for (const f of files) {
|
|
77
|
+
if (typeof f !== 'string' || f.length === 0) {
|
|
78
|
+
throw new NubosPilotError('commit-invalid-path', 'commit path must be non-empty string', { path: f });
|
|
63
79
|
}
|
|
64
80
|
}
|
|
65
81
|
}
|
|
@@ -87,13 +103,15 @@ function run(argv, ctx) {
|
|
|
87
103
|
stdout.write(JSON.stringify({ committed: false, reason: 'commit_artifacts=false', files }) + '\n');
|
|
88
104
|
return 0;
|
|
89
105
|
}
|
|
90
|
-
const
|
|
106
|
+
const root = findProjectRoot(cwd);
|
|
107
|
+
const normalized = _normalizeFiles(files, cwd, root);
|
|
108
|
+
const committable = assertCommittablePaths(normalized, { cwd: root });
|
|
91
109
|
if (committable.length === 0) {
|
|
92
110
|
throw new NubosPilotError('commit-no-paths', 'commit invoked with no committable paths', { files });
|
|
93
111
|
}
|
|
94
|
-
execFileSync('git', ['add', '--', ...committable], { stdio: 'pipe' });
|
|
95
|
-
execFileSync('git', ['commit', '-m', msg, '--', ...committable], { stdio: 'pipe' });
|
|
96
|
-
const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf-8' }).trim();
|
|
112
|
+
execFileSync('git', ['add', '--', ...committable], { cwd: root, stdio: 'pipe' });
|
|
113
|
+
execFileSync('git', ['commit', '-m', msg, '--', ...committable], { cwd: root, stdio: 'pipe' });
|
|
114
|
+
const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root, encoding: 'utf-8' }).trim();
|
|
97
115
|
stdout.write(JSON.stringify({ committed: true, sha, files: committable }) + '\n');
|
|
98
116
|
return 0;
|
|
99
117
|
} catch (err) {
|
|
@@ -57,12 +57,39 @@ test('COMMIT-1: happy path commits a single file and prints sha JSON', () => {
|
|
|
57
57
|
assert.match(stdout.toString(), /"committed":\s*true/);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
test('COMMIT-2: path
|
|
60
|
+
test('COMMIT-2: path resolving outside project root rejected with commit-path-outside-project', () => {
|
|
61
|
+
const sb = makeSandbox();
|
|
62
|
+
initGit(sb);
|
|
63
|
+
const stdout = makeSink();
|
|
64
|
+
const stderr = makeSink();
|
|
65
|
+
const code = commitCli.run(['feat: x', '--files', '../outside.txt'], { cwd: sb, stdout, stderr });
|
|
66
|
+
assert.equal(code, 1);
|
|
67
|
+
assert.match(stderr.toString(), /"code":\s*"commit-path-outside-project"/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('COMMIT-2b: absolute path inside project root is accepted and normalized', () => {
|
|
71
|
+
const sb = makeSandbox();
|
|
72
|
+
initGit(sb);
|
|
73
|
+
const absFile = path.join(sb, 'note.md');
|
|
74
|
+
fs.writeFileSync(absFile, 'hi\n');
|
|
75
|
+
const stdout = makeSink();
|
|
76
|
+
const stderr = makeSink();
|
|
77
|
+
const code = commitCli.run(['docs: note', '--files', absFile], { cwd: sb, stdout, stderr });
|
|
78
|
+
assert.equal(code, 0, 'stderr=' + stderr.toString());
|
|
79
|
+
assert.match(stdout.toString(), /"committed":\s*true/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('COMMIT-2c: absolute path outside project root rejected', () => {
|
|
83
|
+
const sb = makeSandbox();
|
|
84
|
+
initGit(sb);
|
|
85
|
+
const outside = path.join(os.tmpdir(), 'np-commit-outside-' + Date.now() + '.txt');
|
|
86
|
+
fs.writeFileSync(outside, 'x');
|
|
61
87
|
const stdout = makeSink();
|
|
62
88
|
const stderr = makeSink();
|
|
63
|
-
const code = commitCli.run(['
|
|
89
|
+
const code = commitCli.run(['docs: x', '--files', outside], { cwd: sb, stdout, stderr });
|
|
90
|
+
try { fs.unlinkSync(outside); } catch {}
|
|
64
91
|
assert.equal(code, 1);
|
|
65
|
-
assert.match(stderr.toString(), /"code":\s*"commit-path-
|
|
92
|
+
assert.match(stderr.toString(), /"code":\s*"commit-path-outside-project"/);
|
|
66
93
|
});
|
|
67
94
|
|
|
68
95
|
test('COMMIT-3: empty message prints usage and exits 1', () => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
6
|
+
|
|
7
|
+
const PACKAGE_TEMPLATES_DIR = path.resolve(__dirname, '..', '..', 'templates');
|
|
8
|
+
|
|
9
|
+
function _emitError(err, stderr) {
|
|
10
|
+
const code = err && err.name === 'NubosPilotError' ? err.code : 'template-path-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 resolveTemplatePath(name) {
|
|
17
|
+
if (!name || typeof name !== 'string') {
|
|
18
|
+
throw new NubosPilotError('template-invalid-name', 'template name must be a non-empty string', { name });
|
|
19
|
+
}
|
|
20
|
+
const segments = name.split(/[/\\]/);
|
|
21
|
+
for (const seg of segments) {
|
|
22
|
+
if (seg === '' || seg === '..' || seg === '.') {
|
|
23
|
+
throw new NubosPilotError('template-invalid-name', 'template name segment invalid: ' + seg, { name, segment: seg });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const withExt = /\.[a-z0-9]+$/i.test(name) ? name : name + '.md';
|
|
27
|
+
const full = path.resolve(PACKAGE_TEMPLATES_DIR, withExt);
|
|
28
|
+
const guard = PACKAGE_TEMPLATES_DIR + path.sep;
|
|
29
|
+
if (!full.startsWith(guard)) {
|
|
30
|
+
throw new NubosPilotError('template-path-traversal', 'template name escapes templates directory', { name, resolved: full });
|
|
31
|
+
}
|
|
32
|
+
if (!fs.existsSync(full)) {
|
|
33
|
+
throw new NubosPilotError('template-not-found', 'template not found: ' + name, { name, path: full });
|
|
34
|
+
}
|
|
35
|
+
return full;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function run(argv, ctx) {
|
|
39
|
+
const context = ctx || {};
|
|
40
|
+
const stdout = context.stdout || process.stdout;
|
|
41
|
+
const stderr = context.stderr || process.stderr;
|
|
42
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
43
|
+
if (args.length === 0 || args[0] === '--help') {
|
|
44
|
+
stderr.write('Usage: np-tools.cjs template-path <name>\n');
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const out = resolveTemplatePath(args[0]);
|
|
49
|
+
stdout.write(out);
|
|
50
|
+
return 0;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
_emitError(err, stderr);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { run, resolveTemplatePath, PACKAGE_TEMPLATES_DIR };
|
|
58
|
+
|
|
59
|
+
if (require.main === module) {
|
|
60
|
+
process.exit(run(process.argv.slice(2)));
|
|
61
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { test } = require('node:test');
|
|
6
|
+
const assert = require('node:assert/strict');
|
|
7
|
+
const { Writable } = require('node:stream');
|
|
8
|
+
|
|
9
|
+
const cli = require('./template-path.cjs');
|
|
10
|
+
const { resolveTemplatePath, PACKAGE_TEMPLATES_DIR } = cli;
|
|
11
|
+
|
|
12
|
+
function makeSink() {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
const w = new Writable({ write(chunk, _enc, cb) { chunks.push(chunk); cb(); } });
|
|
15
|
+
w.toString = () => Buffer.concat(chunks.map((c) => Buffer.isBuffer(c) ? c : Buffer.from(String(c)))).toString('utf-8');
|
|
16
|
+
return w;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test('TPL-1: resolves VALIDATION to absolute path in package templates dir', () => {
|
|
20
|
+
const out = resolveTemplatePath('VALIDATION');
|
|
21
|
+
assert.ok(path.isAbsolute(out));
|
|
22
|
+
assert.ok(out.startsWith(PACKAGE_TEMPLATES_DIR + path.sep));
|
|
23
|
+
assert.ok(fs.existsSync(out));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('TPL-2: accepts nested name like milestone/CONTEXT', () => {
|
|
27
|
+
const out = resolveTemplatePath('milestone/CONTEXT');
|
|
28
|
+
assert.ok(fs.existsSync(out));
|
|
29
|
+
assert.match(out, /templates[/\\]milestone[/\\]CONTEXT\.md$/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('TPL-3: appends .md when extension absent', () => {
|
|
33
|
+
const out = resolveTemplatePath('VALIDATION');
|
|
34
|
+
assert.ok(out.endsWith('.md'));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('TPL-4: keeps explicit extension', () => {
|
|
38
|
+
const out = resolveTemplatePath('VALIDATION.md');
|
|
39
|
+
assert.ok(out.endsWith('VALIDATION.md'));
|
|
40
|
+
assert.doesNotMatch(out, /\.md\.md$/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('TPL-5: rejects traversal ..', () => {
|
|
44
|
+
assert.throws(
|
|
45
|
+
() => resolveTemplatePath('../etc/passwd'),
|
|
46
|
+
(err) => err && err.code === 'template-invalid-name',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('TPL-6: rejects empty segments', () => {
|
|
51
|
+
assert.throws(
|
|
52
|
+
() => resolveTemplatePath('milestone//CONTEXT'),
|
|
53
|
+
(err) => err && err.code === 'template-invalid-name',
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('TPL-7: rejects non-existent template with template-not-found', () => {
|
|
58
|
+
assert.throws(
|
|
59
|
+
() => resolveTemplatePath('NONEXISTENT'),
|
|
60
|
+
(err) => err && err.code === 'template-not-found',
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('TPL-8: CLI prints path and exits 0 for valid template', () => {
|
|
65
|
+
const stdout = makeSink();
|
|
66
|
+
const stderr = makeSink();
|
|
67
|
+
const code = cli.run(['VALIDATION'], { stdout, stderr });
|
|
68
|
+
assert.equal(code, 0, 'stderr=' + stderr.toString());
|
|
69
|
+
assert.ok(fs.existsSync(stdout.toString()));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('TPL-9: CLI emits error JSON and exits 1 for missing template', () => {
|
|
73
|
+
const stdout = makeSink();
|
|
74
|
+
const stderr = makeSink();
|
|
75
|
+
const code = cli.run(['NOPE'], { stdout, stderr });
|
|
76
|
+
assert.equal(code, 1);
|
|
77
|
+
assert.match(stderr.toString(), /"code":\s*"template-not-found"/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('TPL-10: CLI with no args prints usage to stderr and exits 1', () => {
|
|
81
|
+
const stdout = makeSink();
|
|
82
|
+
const stderr = makeSink();
|
|
83
|
+
const code = cli.run([], { stdout, stderr });
|
|
84
|
+
assert.equal(code, 1);
|
|
85
|
+
assert.match(stderr.toString(), /Usage:/);
|
|
86
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_WORKFLOW = Object.freeze({
|
|
4
|
+
commit_docs: true,
|
|
5
|
+
commit_artifacts: true,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const DEFAULT_AGENTS = Object.freeze({
|
|
9
|
+
parallelization: true,
|
|
10
|
+
research: true,
|
|
11
|
+
plan_checker: true,
|
|
12
|
+
verifier: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MODEL_PROFILE = 'frontier';
|
|
16
|
+
const DEFAULT_SCOPE = 'local';
|
|
17
|
+
const DEFAULT_RESPONSE_LANGUAGE = 'en';
|
|
18
|
+
|
|
19
|
+
function buildInstallConfig(answers) {
|
|
20
|
+
const a = answers || {};
|
|
21
|
+
return {
|
|
22
|
+
runtime: a.runtime || null,
|
|
23
|
+
runtimes: Array.isArray(a.runtimes) ? a.runtimes.slice() : (a.runtime ? [a.runtime] : []),
|
|
24
|
+
scope: a.scope || DEFAULT_SCOPE,
|
|
25
|
+
mcp: !!a.mcp,
|
|
26
|
+
model_profile: a.model_profile || DEFAULT_MODEL_PROFILE,
|
|
27
|
+
response_language: a.response_language || DEFAULT_RESPONSE_LANGUAGE,
|
|
28
|
+
workflow: { ...DEFAULT_WORKFLOW },
|
|
29
|
+
agents: { ...DEFAULT_AGENTS },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
DEFAULT_WORKFLOW,
|
|
35
|
+
DEFAULT_AGENTS,
|
|
36
|
+
DEFAULT_MODEL_PROFILE,
|
|
37
|
+
DEFAULT_SCOPE,
|
|
38
|
+
DEFAULT_RESPONSE_LANGUAGE,
|
|
39
|
+
buildInstallConfig,
|
|
40
|
+
};
|
package/lib/git.cjs
CHANGED
|
@@ -8,12 +8,14 @@ function _isFatalCheckIgnore(err) {
|
|
|
8
8
|
return err && err.status !== 1;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function isPathIgnored(p) {
|
|
11
|
+
function isPathIgnored(p, opts) {
|
|
12
|
+
const spawnOpts = { stdio: 'pipe' };
|
|
13
|
+
if (opts && opts.cwd) spawnOpts.cwd = opts.cwd;
|
|
12
14
|
try {
|
|
13
|
-
execFileSync('git', ['check-ignore', '--quiet', '--', p],
|
|
14
|
-
return true;
|
|
15
|
+
execFileSync('git', ['check-ignore', '--quiet', '--', p], spawnOpts);
|
|
16
|
+
return true;
|
|
15
17
|
} catch (err) {
|
|
16
|
-
if (err && err.status === 1) return false;
|
|
18
|
+
if (err && err.status === 1) return false;
|
|
17
19
|
if (err && err.status === 128) {
|
|
18
20
|
|
|
19
21
|
throw err;
|
|
@@ -22,7 +24,7 @@ function isPathIgnored(p) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
function assertCommittablePaths(paths) {
|
|
27
|
+
function assertCommittablePaths(paths, opts) {
|
|
26
28
|
if (!Array.isArray(paths)) {
|
|
27
29
|
throw new NubosPilotError(
|
|
28
30
|
'commit-paths-invalid',
|
|
@@ -30,11 +32,13 @@ function assertCommittablePaths(paths) {
|
|
|
30
32
|
{ got: typeof paths },
|
|
31
33
|
);
|
|
32
34
|
}
|
|
35
|
+
const spawnOpts = { stdio: 'pipe' };
|
|
36
|
+
if (opts && opts.cwd) spawnOpts.cwd = opts.cwd;
|
|
33
37
|
const ignored = [];
|
|
34
38
|
for (const p of paths) {
|
|
35
39
|
try {
|
|
36
|
-
execFileSync('git', ['check-ignore', '--quiet', '--', p],
|
|
37
|
-
ignored.push(p);
|
|
40
|
+
execFileSync('git', ['check-ignore', '--quiet', '--', p], spawnOpts);
|
|
41
|
+
ignored.push(p);
|
|
38
42
|
} catch (err) {
|
|
39
43
|
if (_isFatalCheckIgnore(err)) {
|
|
40
44
|
if (err.status === 128) throw err;
|
package/np-tools.cjs
CHANGED
|
@@ -48,6 +48,7 @@ const topLevelCommands = {
|
|
|
48
48
|
'lang-directive': require('./bin/np-tools/lang-directive.cjs'),
|
|
49
49
|
'text-mode': require('./bin/np-tools/text-mode.cjs'),
|
|
50
50
|
'detect-runtime': require('./bin/np-tools/detect-runtime.cjs'),
|
|
51
|
+
'template-path': require('./bin/np-tools/template-path.cjs'),
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
const THRESHOLD = 16 * 1024;
|
package/package.json
CHANGED
|
@@ -356,13 +356,14 @@ CONTEXT_PATH=$(echo "$INIT" | node -e 'let d="";process.stdin.on("data",c=>d+=c)
|
|
|
356
356
|
mkdir -p "$MILESTONE_DIR"
|
|
357
357
|
mkdir -p "$MILESTONE_DIR/slices"
|
|
358
358
|
|
|
359
|
+
TPL_PATH=$(node .nubos-pilot/bin/np-tools.cjs template-path milestone/CONTEXT)
|
|
359
360
|
node -e '
|
|
360
361
|
const { render } = require("./lib/template.cjs");
|
|
361
362
|
const fs = require("node:fs");
|
|
362
|
-
const tpl = fs.readFileSync(
|
|
363
|
-
const vars = JSON.parse(process.argv[
|
|
363
|
+
const tpl = fs.readFileSync(process.argv[1], "utf-8");
|
|
364
|
+
const vars = JSON.parse(process.argv[2]);
|
|
364
365
|
process.stdout.write(render(tpl, vars));
|
|
365
|
-
' "$VARS_JSON" > "$CONTEXT_PATH"
|
|
366
|
+
' "$TPL_PATH" "$VARS_JSON" > "$CONTEXT_PATH"
|
|
366
367
|
```
|
|
367
368
|
|
|
368
369
|
`$VARS_JSON` is the JSON-serialised accumulator from Steps 2–5 (keys map to
|
|
@@ -42,7 +42,7 @@ the main chat. Auto-enabled in Claude Code (CLAUDECODE=1); opt-in via
|
|
|
42
42
|
MILESTONE_ID=$(echo "$INIT" | jq -r '.milestone_id')
|
|
43
43
|
MILESTONE_DIR=$(echo "$INIT" | jq -r '.milestone_dir')
|
|
44
44
|
VALIDATION_PATH="${MILESTONE_DIR}/${MILESTONE_ID}-VALIDATION.md"
|
|
45
|
-
TEMPLATE_PATH
|
|
45
|
+
TEMPLATE_PATH=$(node .nubos-pilot/bin/np-tools.cjs template-path VALIDATION)
|
|
46
46
|
REQS_PATH=".nubos-pilot/REQUIREMENTS.md"
|
|
47
47
|
PLAN_ID="${MILESTONE_ID}-validate"
|
|
48
48
|
TASK_ID="${MILESTONE_ID}-validate"
|
|
@@ -93,9 +93,9 @@ fi
|
|
|
93
93
|
### Gate 3 — Template present
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
if [[ ! -f "$TEMPLATE_PATH" ]]; then
|
|
97
|
-
echo "Error:
|
|
98
|
-
echo "Re-run 'npx nubos-pilot install' or
|
|
96
|
+
if [[ -z "$TEMPLATE_PATH" || ! -f "$TEMPLATE_PATH" ]]; then
|
|
97
|
+
echo "Error: VALIDATION template not resolvable via np-tools.cjs template-path." >&2
|
|
98
|
+
echo "Re-run 'npx nubos-pilot install' or check the package's templates/ dir." >&2
|
|
99
99
|
exit 1
|
|
100
100
|
fi
|
|
101
101
|
```
|