nubos-pilot 0.5.7 → 0.5.8

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.
@@ -2,6 +2,7 @@ const { execFileSync } = require('node:child_process');
2
2
  const path = require('node:path');
3
3
  const { NubosPilotError } = require('../../lib/core.cjs');
4
4
  const { assertCommittablePaths } = require('../../lib/git.cjs');
5
+ const { resolveCommitArtifacts } = require('../../lib/commit-policy.cjs');
5
6
 
6
7
  const MAX_MSG = 2000;
7
8
 
@@ -65,6 +66,7 @@ function _validateFiles(files) {
65
66
 
66
67
  function run(argv, ctx) {
67
68
  const context = ctx || {};
69
+ const cwd = context.cwd || process.cwd();
68
70
  const stdout = context.stdout || process.stdout;
69
71
  const stderr = context.stderr || process.stderr;
70
72
  try {
@@ -81,6 +83,10 @@ function run(argv, ctx) {
81
83
  return 1;
82
84
  }
83
85
  _validateFiles(files);
86
+ if (resolveCommitArtifacts(cwd) === false) {
87
+ stdout.write(JSON.stringify({ committed: false, reason: 'commit_artifacts=false', files }) + '\n');
88
+ return 0;
89
+ }
84
90
  const committable = assertCommittablePaths(files);
85
91
  if (committable.length === 0) {
86
92
  throw new NubosPilotError('commit-no-paths', 'commit invoked with no committable paths', { files });
@@ -91,3 +91,47 @@ test('COMMIT-4: overlong message exceeds limit → commit-message-too-long', ()
91
91
  assert.equal(code, 1);
92
92
  assert.match(stderr.toString(), /"code":\s*"commit-message-too-long"/);
93
93
  });
94
+
95
+ test('COMMIT-5: workflow.commit_artifacts=false skips commit silently with exit 0', () => {
96
+ const sb = makeSandbox();
97
+ initGit(sb);
98
+ fs.writeFileSync(path.join(sb, 'note.md'), 'x');
99
+ fs.writeFileSync(
100
+ path.join(sb, '.nubos-pilot', 'config.json'),
101
+ JSON.stringify({ workflow: { commit_artifacts: false } }),
102
+ );
103
+ const stdout = makeSink();
104
+ const stderr = makeSink();
105
+ const code = commitCli.run(['docs: note', '--files', 'note.md'], { cwd: sb, stdout, stderr });
106
+ assert.equal(code, 0, 'stderr=' + stderr.toString());
107
+ const out = stdout.toString();
108
+ assert.match(out, /"committed":\s*false/);
109
+ assert.match(out, /"reason":\s*"commit_artifacts=false"/);
110
+ let logOut = '';
111
+ try {
112
+ logOut = execFileSync('git', ['log', '--oneline'], { cwd: sb, encoding: 'utf-8' }).trim();
113
+ } catch { logOut = ''; }
114
+ assert.equal(logOut, '', 'expected no commits to be created');
115
+ });
116
+
117
+ test('COMMIT-6: workflow.commit_artifacts=true still commits normally', () => {
118
+ const sb = makeSandbox();
119
+ initGit(sb);
120
+ fs.writeFileSync(path.join(sb, 'note.md'), 'x');
121
+ fs.writeFileSync(
122
+ path.join(sb, '.nubos-pilot', 'config.json'),
123
+ JSON.stringify({ workflow: { commit_artifacts: true } }),
124
+ );
125
+ const stdout = makeSink();
126
+ const stderr = makeSink();
127
+ const origCwd = process.cwd();
128
+ process.chdir(sb);
129
+ let code;
130
+ try {
131
+ code = commitCli.run(['docs: note', '--files', 'note.md'], { stdout, stderr });
132
+ } finally {
133
+ process.chdir(origCwd);
134
+ }
135
+ assert.equal(code, 0, 'stderr=' + stderr.toString());
136
+ assert.match(stdout.toString(), /"committed":\s*true/);
137
+ });
package/lib/agents.cjs CHANGED
@@ -50,16 +50,22 @@ function validateAgentFrontmatter(fm, agentName) {
50
50
  }
51
51
 
52
52
  function loadAgent(name, cwd) {
53
- const root = findProjectRoot(cwd || process.cwd());
54
- const p = path.join(root, 'agents', name + '.md');
55
- if (!fs.existsSync(p)) {
53
+ const candidates = [];
54
+ try {
55
+ const root = findProjectRoot(cwd || process.cwd());
56
+ candidates.push(path.join(root, 'agents', name + '.md'));
57
+ } catch {}
58
+ candidates.push(path.resolve(__dirname, '..', 'agents', name + '.md'));
59
+
60
+ const found = candidates.find((p) => fs.existsSync(p));
61
+ if (!found) {
56
62
  throw new NubosPilotError(
57
63
  'agent-not-found',
58
- 'Agent "' + name + '" not found at ' + p,
59
- { name, path: p },
64
+ 'Agent "' + name + '" not found at ' + candidates[0],
65
+ { name, path: candidates[0], tried: candidates },
60
66
  );
61
67
  }
62
- const { frontmatter } = extractFrontmatter(fs.readFileSync(p, 'utf-8'));
68
+ const { frontmatter } = extractFrontmatter(fs.readFileSync(found, 'utf-8'));
63
69
  return validateAgentFrontmatter(frontmatter, name);
64
70
  }
65
71
 
@@ -0,0 +1,60 @@
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 DEFAULT_COMMIT_ARTIFACTS = true;
8
+
9
+ function _coerceBool(raw) {
10
+ if (raw === true || raw === false) return raw;
11
+ if (raw == null) return null;
12
+ const s = String(raw).trim().toLowerCase();
13
+ if (s === 'true' || s === '1' || s === 'yes' || s === 'on') return true;
14
+ if (s === 'false' || s === '0' || s === 'no' || s === 'off') return false;
15
+ return null;
16
+ }
17
+
18
+ function readConfigCommitArtifacts(cwd) {
19
+ let root;
20
+ try {
21
+ root = findProjectRoot(cwd || process.cwd());
22
+ } catch (err) {
23
+ if (err && err.code === 'not-in-project') return null;
24
+ throw err;
25
+ }
26
+ const p = path.join(root, '.nubos-pilot', 'config.json');
27
+ if (!fs.existsSync(p)) return null;
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
31
+ } catch (err) {
32
+ throw new NubosPilotError('commit-policy-config-parse-error', 'config.json invalid JSON', { cause: err && err.message });
33
+ }
34
+ if (!parsed || typeof parsed !== 'object') return null;
35
+ const workflow = parsed.workflow;
36
+ if (!workflow || typeof workflow !== 'object') return null;
37
+ if (!Object.prototype.hasOwnProperty.call(workflow, 'commit_artifacts')) return null;
38
+ return _coerceBool(workflow.commit_artifacts);
39
+ }
40
+
41
+ function resolveCommitArtifacts(cwd) {
42
+ const fromConfig = readConfigCommitArtifacts(cwd);
43
+ if (fromConfig !== null) return fromConfig;
44
+ return DEFAULT_COMMIT_ARTIFACTS;
45
+ }
46
+
47
+ function resolveCommitArtifactsDetail(cwd) {
48
+ const fromConfig = readConfigCommitArtifacts(cwd);
49
+ if (fromConfig !== null) {
50
+ return { enabled: fromConfig, source: 'config' };
51
+ }
52
+ return { enabled: DEFAULT_COMMIT_ARTIFACTS, source: 'default' };
53
+ }
54
+
55
+ module.exports = {
56
+ DEFAULT_COMMIT_ARTIFACTS,
57
+ readConfigCommitArtifacts,
58
+ resolveCommitArtifacts,
59
+ resolveCommitArtifactsDetail,
60
+ };
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const { test } = require('node:test');
7
+ const assert = require('node:assert/strict');
8
+
9
+ const {
10
+ DEFAULT_COMMIT_ARTIFACTS,
11
+ readConfigCommitArtifacts,
12
+ resolveCommitArtifacts,
13
+ resolveCommitArtifactsDetail,
14
+ } = require('./commit-policy.cjs');
15
+
16
+ const _sandboxes = [];
17
+
18
+ function makeSandbox(config) {
19
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-commit-policy-'));
20
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
21
+ if (config !== undefined) {
22
+ fs.writeFileSync(
23
+ path.join(root, '.nubos-pilot', 'config.json'),
24
+ typeof config === 'string' ? config : JSON.stringify(config),
25
+ );
26
+ }
27
+ _sandboxes.push(root);
28
+ return root;
29
+ }
30
+
31
+ test.afterEach(() => {
32
+ while (_sandboxes.length) {
33
+ try { fs.rmSync(_sandboxes.pop(), { recursive: true, force: true }); } catch { }
34
+ }
35
+ });
36
+
37
+ test('CP-1: default is true when config absent', () => {
38
+ const sb = makeSandbox();
39
+ assert.equal(resolveCommitArtifacts(sb), true);
40
+ assert.equal(DEFAULT_COMMIT_ARTIFACTS, true);
41
+ });
42
+
43
+ test('CP-2: workflow.commit_artifacts=false is respected', () => {
44
+ const sb = makeSandbox({ workflow: { commit_artifacts: false } });
45
+ assert.equal(resolveCommitArtifacts(sb), false);
46
+ assert.deepEqual(resolveCommitArtifactsDetail(sb), { enabled: false, source: 'config' });
47
+ });
48
+
49
+ test('CP-3: workflow.commit_artifacts=true is respected', () => {
50
+ const sb = makeSandbox({ workflow: { commit_artifacts: true } });
51
+ assert.equal(resolveCommitArtifacts(sb), true);
52
+ assert.deepEqual(resolveCommitArtifactsDetail(sb), { enabled: true, source: 'config' });
53
+ });
54
+
55
+ test('CP-4: missing workflow.commit_artifacts key falls back to default', () => {
56
+ const sb = makeSandbox({ workflow: { text_mode: true } });
57
+ assert.equal(resolveCommitArtifacts(sb), true);
58
+ assert.deepEqual(resolveCommitArtifactsDetail(sb), { enabled: true, source: 'default' });
59
+ });
60
+
61
+ test('CP-5: invalid JSON surfaces as commit-policy-config-parse-error', () => {
62
+ const sb = makeSandbox('{ "workflow": { "commit_artifacts": false ');
63
+ assert.throws(
64
+ () => readConfigCommitArtifacts(sb),
65
+ (err) => err && err.code === 'commit-policy-config-parse-error',
66
+ );
67
+ });
68
+
69
+ test('CP-6: string "false" / "off" / "0" coerce to false', () => {
70
+ for (const val of ['false', 'off', '0', 'no']) {
71
+ const sb = makeSandbox({ workflow: { commit_artifacts: val } });
72
+ assert.equal(resolveCommitArtifacts(sb), false, 'expected ' + val + ' → false');
73
+ }
74
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nubos-pilot",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "AI-driven planning and execution tool for code projects",
5
5
  "homepage": "https://github.com/Nubos-AI/nubos-pilot",
6
6
  "repository": {
@@ -17,7 +17,7 @@ sentinels survive regeneration.
17
17
 
18
18
  ```bash
19
19
  PHASE="$1"
20
- INIT=$(node .nubos-pilot/bin/np-tools.cjs init add-tests "$PHASE")
20
+ INIT=$(node .nubos-pilot/bin/np-tools.cjs init add-tests init "$PHASE")
21
21
  ```
22
22
 
23
23
  Parse: `phase`, `target_path`, `verification_path`, `pass_cases[]`,
@@ -277,8 +277,11 @@ The scaffolder:
277
277
  ## Commit
278
278
 
279
279
  ```bash
280
- git add "$milestone_dir"
281
- git commit -m "docs(${milestone_id}): milestone plan ready for execute"
280
+ COMMIT_ARTIFACTS=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.commit_artifacts 2>/dev/null || echo "true")
281
+ if [[ "$COMMIT_ARTIFACTS" != "false" ]]; then
282
+ git add "$milestone_dir"
283
+ git commit -m "docs(${milestone_id}): milestone plan ready for execute"
284
+ fi
282
285
  ```
283
286
 
284
287
  Commits include: all milestone-level artefacts (CONTEXT/ROADMAP/META), every slice's ASSESSMENT/PLAN/UAT, and every scaffolded task file.
@@ -20,7 +20,7 @@ if [[ -z "$PHASE" ]]; then
20
20
  fi
21
21
 
22
22
  LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
23
- INIT=$(node .nubos-pilot/bin/np-tools.cjs init verify-work "$PHASE")
23
+ INIT=$(node .nubos-pilot/bin/np-tools.cjs init verify-work init "$PHASE")
24
24
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
25
25
  RUNTIME=$(node .nubos-pilot/bin/np-tools.cjs detect-runtime)
26
26
  ```
@@ -17,7 +17,7 @@ Slice-level acceptance (UAT) is validated separately by `/np:validate-phase <N>`
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 verify-work "$PHASE")
20
+ INIT=$(node .nubos-pilot/bin/np-tools.cjs init verify-work init "$PHASE")
21
21
  if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
22
22
  AGENT_SKILLS_VERIFIER=$(node .nubos-pilot/bin/np-tools.cjs agent-skills verifier 2>/dev/null)
23
23
  ```