nubos-pilot 1.3.1 → 1.3.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.
@@ -0,0 +1,131 @@
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, afterEach } = require('node:test');
7
+ const assert = require('node:assert/strict');
8
+
9
+ const debt = require('./economy-debt.cjs');
10
+
11
+ const _sandboxes = [];
12
+
13
+ function makeSandbox() {
14
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-economy-debt-'));
15
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
16
+ _sandboxes.push(root);
17
+ return root;
18
+ }
19
+
20
+ afterEach(() => {
21
+ while (_sandboxes.length) {
22
+ try { fs.rmSync(_sandboxes.pop(), { recursive: true, force: true }); } catch { /* best effort */ }
23
+ }
24
+ });
25
+
26
+ test('ED-1: addEntry writes an open entry and returns was_new=true', () => {
27
+ const cwd = makeSandbox();
28
+ const e = debt.addEntry(
29
+ { file: 'src/foo.ts', line: 42, category: 'over-engineering', note: 'Single-use factory — inline it.' },
30
+ cwd,
31
+ );
32
+ assert.equal(e.was_new, true);
33
+ assert.equal(e.status, 'open');
34
+ assert.equal(e.category, 'over-engineering');
35
+ assert.equal(e.file, 'src/foo.ts');
36
+ assert.equal(e.line, 42);
37
+ assert.match(e.id, /^[0-9a-f]{7}$/);
38
+ assert.ok(fs.existsSync(e.path));
39
+ });
40
+
41
+ test('ED-2: addEntry is idempotent — identical input does not duplicate', () => {
42
+ const cwd = makeSandbox();
43
+ const first = debt.addEntry(
44
+ { file: 'a.ts', line: 1, category: 'shrinkable', note: 'manual reduce -> Array.reduce' },
45
+ cwd,
46
+ );
47
+ const second = debt.addEntry(
48
+ { file: 'a.ts', line: 1, category: 'shrinkable', note: 'manual reduce -> Array.reduce' },
49
+ cwd,
50
+ );
51
+ assert.equal(first.id, second.id);
52
+ assert.equal(second.was_new, false);
53
+ assert.equal(debt.listEntries('open', cwd).length, 1);
54
+ });
55
+
56
+ test('ED-3: addEntry rejects a category outside the four economy routes', () => {
57
+ const cwd = makeSandbox();
58
+ assert.throws(
59
+ () => debt.addEntry({ file: 'a.ts', category: 'security', note: 'x' }, cwd),
60
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-invalid-category',
61
+ );
62
+ });
63
+
64
+ test('ED-4: addEntry rejects an empty note', () => {
65
+ const cwd = makeSandbox();
66
+ assert.throws(
67
+ () => debt.addEntry({ file: 'a.ts', category: 'shrinkable', note: ' ' }, cwd),
68
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-missing-note',
69
+ );
70
+ });
71
+
72
+ test('ED-5: line defaults to 0 (file-level) when omitted', () => {
73
+ const cwd = makeSandbox();
74
+ const e = debt.addEntry({ file: 'a.ts', category: 'native-duplication', note: 'reimplements framework helper' }, cwd);
75
+ assert.equal(e.line, 0);
76
+ const parsed = debt.listEntries('open', cwd)[0];
77
+ assert.equal(parsed.line, 0);
78
+ });
79
+
80
+ test('ED-6: listEntries sorts oldest-first and round-trips note + fields', () => {
81
+ const cwd = makeSandbox();
82
+ debt.addEntry({ file: 'a.ts', line: 5, category: 'shrinkable', note: 'first' }, cwd);
83
+ debt.addEntry({ file: 'b.ts', line: 9, category: 'over-engineering', note: 'second' }, cwd);
84
+ const list = debt.listEntries('open', cwd);
85
+ assert.equal(list.length, 2);
86
+ assert.equal(list[0].note, 'first');
87
+ assert.equal(list[1].note, 'second');
88
+ assert.equal(list[1].category, 'over-engineering');
89
+ assert.equal(list[1].line, 9);
90
+ });
91
+
92
+ test('ED-7: resolveEntry moves open -> resolved and stamps resolved time', () => {
93
+ const cwd = makeSandbox();
94
+ const e = debt.addEntry({ file: 'a.ts', line: 1, category: 'stdlib-reinvention', note: 'hand-rolled clamp' }, cwd);
95
+ const r = debt.resolveEntry(e.id, cwd);
96
+ assert.equal(r.status, 'resolved');
97
+ assert.match(r.resolved, /^\d{4}-\d{2}-\d{2}T/);
98
+ assert.equal(debt.listEntries('open', cwd).length, 0);
99
+ assert.equal(debt.listEntries('resolved', cwd).length, 1);
100
+ assert.equal(debt.listEntries('all', cwd).length, 1);
101
+ assert.ok(!fs.existsSync(e.path));
102
+ });
103
+
104
+ test('ED-8: resolveEntry throws economy-debt-not-found for an unknown id', () => {
105
+ const cwd = makeSandbox();
106
+ assert.throws(
107
+ () => debt.resolveEntry('deadbee', cwd),
108
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-not-found',
109
+ );
110
+ });
111
+
112
+ test('ED-9: listEntries rejects an invalid status', () => {
113
+ const cwd = makeSandbox();
114
+ assert.throws(
115
+ () => debt.listEntries('bogus', cwd),
116
+ (err) => err && err.name === 'NubosPilotError' && err.code === 'economy-debt-invalid-status',
117
+ );
118
+ });
119
+
120
+ test('ED-10: empty ledger lists as []', () => {
121
+ const cwd = makeSandbox();
122
+ assert.deepEqual(debt.listEntries('open', cwd), []);
123
+ assert.deepEqual(debt.listEntries('all', cwd), []);
124
+ });
125
+
126
+ test('ED-11: ECONOMY_CATEGORIES matches the four canonical economy routes', () => {
127
+ assert.deepEqual(
128
+ debt.ECONOMY_CATEGORIES.slice().sort(),
129
+ ['native-duplication', 'over-engineering', 'shrinkable', 'stdlib-reinvention'],
130
+ );
131
+ });
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ // Single source of truth for the Economy axis activation level (Ponytail-style
4
+ // graduated modes). The Economy schicht has two mechanisms — the prevention
5
+ // ladder in agents/np-executor.md (guidance BEFORE writing) and the in-loop
6
+ // Economy critic (agents/np-critic-economy.md, audits the diff AFTER). One
7
+ // enum dials both:
8
+ //
9
+ // off prevention OFF, critic OFF — no economy pressure at all
10
+ // lite prevention ON, critic OFF — prevention-first DEFAULT (advisory only)
11
+ // full prevention ON, critic ON — standard critic rubric
12
+ // ultra prevention ON, critic ON — aggressive critic (lowered shrinkable bar)
13
+ //
14
+ // Default is `lite`: the climb-the-ladder discipline is on, but nothing bounces
15
+ // work back. This makes prevention-first the documented default philosophy
16
+ // while keeping the costlier critic opt-in (full/ultra).
17
+ //
18
+ // Backward-compat: the legacy boolean `agents.economy_critic` is honoured when
19
+ // `agents.economy` is absent — true→full, false→lite — so a pre-existing
20
+ // gitignored config keeps its behaviour. The resolver is LOUD: an explicit but
21
+ // invalid `agents.economy` string throws rather than silently defaulting.
22
+
23
+ const { NubosPilotError } = require('./core.cjs');
24
+
25
+ const VALID_ECONOMY_MODES = Object.freeze(['off', 'lite', 'full', 'ultra']);
26
+ const DEFAULT_ECONOMY_MODE = 'lite';
27
+
28
+ function resolveEconomyMode(config) {
29
+ const agents = config && typeof config === 'object' ? config.agents : null;
30
+ if (agents && typeof agents === 'object') {
31
+ if (agents.economy !== undefined) {
32
+ const explicit = agents.economy;
33
+ if (typeof explicit !== 'string' || !VALID_ECONOMY_MODES.includes(explicit)) {
34
+ throw new NubosPilotError(
35
+ 'config-invalid-economy-mode',
36
+ 'agents.economy must be one of ' + VALID_ECONOMY_MODES.join('|') + ' (got: ' + JSON.stringify(explicit) + ')',
37
+ { value: explicit, valid: VALID_ECONOMY_MODES },
38
+ );
39
+ }
40
+ return explicit;
41
+ }
42
+ if (typeof agents.economy_critic === 'boolean') {
43
+ return agents.economy_critic ? 'full' : 'lite';
44
+ }
45
+ }
46
+ return DEFAULT_ECONOMY_MODE;
47
+ }
48
+
49
+ function preventionOn(mode) { return mode !== 'off'; }
50
+ function criticOn(mode) { return mode === 'full' || mode === 'ultra'; }
51
+ function isUltra(mode) { return mode === 'ultra'; }
52
+
53
+ function economyFlags(config) {
54
+ const mode = resolveEconomyMode(config);
55
+ return { mode, prevention: preventionOn(mode), critic: criticOn(mode), ultra: isUltra(mode) };
56
+ }
57
+
58
+ module.exports = {
59
+ VALID_ECONOMY_MODES,
60
+ DEFAULT_ECONOMY_MODE,
61
+ resolveEconomyMode,
62
+ preventionOn,
63
+ criticOn,
64
+ isUltra,
65
+ economyFlags,
66
+ };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+
6
+ const {
7
+ VALID_ECONOMY_MODES,
8
+ DEFAULT_ECONOMY_MODE,
9
+ resolveEconomyMode,
10
+ preventionOn,
11
+ criticOn,
12
+ isUltra,
13
+ economyFlags,
14
+ } = require('./economy-mode.cjs');
15
+
16
+ test('default is lite (prevention-first) when nothing is set', () => {
17
+ assert.equal(DEFAULT_ECONOMY_MODE, 'lite');
18
+ assert.equal(resolveEconomyMode({}), 'lite');
19
+ assert.equal(resolveEconomyMode({ agents: {} }), 'lite');
20
+ assert.equal(resolveEconomyMode(null), 'lite');
21
+ assert.equal(resolveEconomyMode(undefined), 'lite');
22
+ });
23
+
24
+ test('explicit agents.economy wins for every valid mode', () => {
25
+ for (const mode of VALID_ECONOMY_MODES) {
26
+ assert.equal(resolveEconomyMode({ agents: { economy: mode } }), mode);
27
+ }
28
+ });
29
+
30
+ test('legacy agents.economy_critic maps true→full, false→lite', () => {
31
+ assert.equal(resolveEconomyMode({ agents: { economy_critic: true } }), 'full');
32
+ assert.equal(resolveEconomyMode({ agents: { economy_critic: false } }), 'lite');
33
+ });
34
+
35
+ test('non-boolean legacy economy_critic falls back to the lite default (schema warns separately)', () => {
36
+ assert.equal(resolveEconomyMode({ agents: { economy_critic: 'true' } }), 'lite');
37
+ assert.equal(resolveEconomyMode({ agents: { economy_critic: 1 } }), 'lite');
38
+ assert.equal(resolveEconomyMode({ agents: { economy_critic: null } }), 'lite');
39
+ });
40
+
41
+ test('explicit economy overrides the legacy bool', () => {
42
+ assert.equal(resolveEconomyMode({ agents: { economy: 'off', economy_critic: true } }), 'off');
43
+ assert.equal(resolveEconomyMode({ agents: { economy: 'ultra', economy_critic: false } }), 'ultra');
44
+ });
45
+
46
+ test('invalid explicit economy throws loud (no silent default)', () => {
47
+ assert.throws(
48
+ () => resolveEconomyMode({ agents: { economy: 'banana' } }),
49
+ (err) => err.code === 'config-invalid-economy-mode',
50
+ );
51
+ assert.throws(
52
+ () => resolveEconomyMode({ agents: { economy: 42 } }),
53
+ (err) => err.code === 'config-invalid-economy-mode',
54
+ );
55
+ });
56
+
57
+ test('flag helpers gate prevention/critic/ultra correctly', () => {
58
+ assert.equal(preventionOn('off'), false);
59
+ assert.equal(preventionOn('lite'), true);
60
+ assert.equal(preventionOn('full'), true);
61
+ assert.equal(preventionOn('ultra'), true);
62
+
63
+ assert.equal(criticOn('off'), false);
64
+ assert.equal(criticOn('lite'), false);
65
+ assert.equal(criticOn('full'), true);
66
+ assert.equal(criticOn('ultra'), true);
67
+
68
+ assert.equal(isUltra('ultra'), true);
69
+ assert.equal(isUltra('full'), false);
70
+ });
71
+
72
+ test('economyFlags bundles the resolved mode with its gates', () => {
73
+ assert.deepEqual(economyFlags({ agents: { economy: 'off' } }), {
74
+ mode: 'off', prevention: false, critic: false, ultra: false,
75
+ });
76
+ assert.deepEqual(economyFlags({}), {
77
+ mode: 'lite', prevention: true, critic: false, ultra: false,
78
+ });
79
+ assert.deepEqual(economyFlags({ agents: { economy: 'full' } }), {
80
+ mode: 'full', prevention: true, critic: true, ultra: false,
81
+ });
82
+ assert.deepEqual(economyFlags({ agents: { economy: 'ultra' } }), {
83
+ mode: 'ultra', prevention: true, critic: true, ultra: true,
84
+ });
85
+ });
package/lib/git.cjs CHANGED
@@ -92,7 +92,7 @@ function commitTask(taskId, files, message) {
92
92
  };
93
93
  }
94
94
 
95
- function findCommitByTaskId(id) {
95
+ function findCommitByTaskId(id, cwd) {
96
96
  if (typeof id !== 'string' || !TASK_ID_RE.test(id)) {
97
97
  throw new NubosPilotError(
98
98
  'task-commit-not-found',
@@ -101,6 +101,8 @@ function findCommitByTaskId(id) {
101
101
  );
102
102
  }
103
103
 
104
+ const spawnOpts = { encoding: 'utf-8', timeout: GIT_TIMEOUT_MS };
105
+ if (cwd) spawnOpts.cwd = cwd;
104
106
  const out = execFileSync(
105
107
  'git',
106
108
  [
@@ -112,7 +114,7 @@ function findCommitByTaskId(id) {
112
114
  '1',
113
115
  '--format=%H',
114
116
  ],
115
- { encoding: 'utf-8', timeout: GIT_TIMEOUT_MS },
117
+ spawnOpts,
116
118
  ).trim();
117
119
  if (!out) {
118
120
  throw new NubosPilotError(
package/lib/nubosloop.cjs CHANGED
@@ -34,6 +34,10 @@ const ROUTE_TABLE = {
34
34
  'verify-mismatch': 'executor',
35
35
  'unmet-criterion': 'executor',
36
36
  'scope-creep': 'executor',
37
+ 'over-engineering': 'executor',
38
+ 'stdlib-reinvention': 'executor',
39
+ 'native-duplication': 'executor',
40
+ 'shrinkable': 'executor',
37
41
  'information-missing': 'researcher',
38
42
  'question-to-user': 'askuser',
39
43
  'locked-decision-violation': 'plan-checker',
@@ -191,6 +191,7 @@ test('NL-17: ROUTE_TABLE covers every documented finding category', () => {
191
191
  'missing-test', 'edge-case-gap',
192
192
  'weak-assertion', 'silenced-failure', 'test-naming', 'non-deterministic',
193
193
  'verify-mismatch', 'unmet-criterion', 'scope-creep', 'information-missing',
194
+ 'over-engineering', 'stdlib-reinvention', 'native-duplication', 'shrinkable',
194
195
  'infrastructure-mismatch',
195
196
  'question-to-user', 'locked-decision-violation', 'stuck-detected',
196
197
  ];
package/np-tools.cjs CHANGED
@@ -44,6 +44,7 @@ const topLevelCommands = {
44
44
  'askuser': require('./bin/np-tools/askuser.cjs'),
45
45
  'commit': require('./bin/np-tools/commit.cjs'),
46
46
  'config-get': require('./bin/np-tools/config.cjs'),
47
+ 'economy-mode': require('./bin/np-tools/economy-mode.cjs'),
47
48
  'scan-codebase': require('./bin/np-tools/scan-codebase.cjs'),
48
49
  'update-docs': require('./bin/np-tools/update-docs.cjs'),
49
50
  'graph-impact': require('./bin/np-tools/graph-impact.cjs'),
@@ -79,6 +80,7 @@ const topLevelCommands = {
79
80
  'worktree-list': require('./bin/np-tools/worktree-list.cjs'),
80
81
  'worktree-ff-merge': require('./bin/np-tools/worktree-ff-merge.cjs'),
81
82
  'dashboard': require('./bin/np-tools/dashboard.cjs'),
83
+ 'simplify-debt': require('./bin/np-tools/simplify-debt.cjs'),
82
84
  'archive-project': require('./bin/np-tools/archive-project.cjs'),
83
85
 
84
86
  ...initWorkflows,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nubos-pilot",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Self-hosted AI pilot for any codebase. Researcher and critic agents plan, execute and verify each change.",
5
5
  "homepage": "https://pilot.nubos.cloud",
6
6
  "repository": {
@@ -35,8 +35,14 @@ RUNTIME=$(node .nubos-pilot/bin/np-tools.cjs detect-runtime)
35
35
  WORKTREE_ISOLATION=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.worktree_isolation 2>/dev/null || echo "false")
36
36
  TIER_ROUTING=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.tier_routing 2>/dev/null || echo "false")
37
37
  VERIFY_RUNS=$(node .nubos-pilot/bin/np-tools.cjs config-get loop.verify_runs 2>/dev/null || echo "1")
38
+ ECONOMY=$(node .nubos-pilot/bin/np-tools.cjs economy-mode --json 2>/dev/null || echo '{"mode":"lite","prevention":true,"critic":false,"ultra":false}')
39
+ ECONOMY_MODE=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).mode)}catch{console.log("lite")}})')
40
+ ECONOMY_PREVENTION=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).prevention)}catch{console.log("true")}})')
41
+ ECONOMY_CRITIC=$(echo "$ECONOMY" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).critic)}catch{console.log("false")}})')
38
42
  ```
39
43
 
44
+ **Economy axis (Ponytail-style graduated modes, SSOT = `economy-mode`).** `$ECONOMY_MODE` is one of `off|lite|full|ultra` (default `lite` = prevention-first). It dials two mechanisms: `$ECONOMY_PREVENTION` (`true` for `lite`/`full`/`ultra`) gates the climb-the-ladder directive injected into the Executor (Step 3); `$ECONOMY_CRITIC` (`true` for `full`/`ultra`) gates the `np-critic-economy.md` audit module injected into np-critic (Step 5). `ultra` additionally tells the critic to lower its `shrinkable` bar. Resolve this ONCE here — never re-read the raw config toggle downstream.
45
+
40
46
  When `--verify-work` is passed, the init payload's `auto_verify: true` flag tells this workflow to chain into `/np:verify-work $PHASE` after every slice committed and `finalize-milestone` ran. Without the flag the workflow stops after finalize as before — verify-work then remains a separate manual step.
41
47
 
42
48
  **Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
@@ -118,16 +124,17 @@ If zero skills match, omit the block — do **not** invent skills. Adding new sk
118
124
 
119
125
  ## Pre-Flight — orphan-checkpoint guard
120
126
 
121
- Detect stale checkpoints from a prior run before starting new work:
127
+ Detect stale checkpoints from a prior run before starting new work. `init resume-work` first **reconciles every checkpoint against git** (`lib/checkpoint-reconcile.cjs`): any checkpoint whose task already has a `task(<id>):` commit is a tombstone (finishTask never unlinked it — crash between commit and unlink, or a commit made outside `commit-task`) and is pruned silently. They surface in `RESUME.pruned_checkpoints` for the log, never as a prompt. Only genuinely **uncommitted** checkpoints reach `status: orphan` and the dialog below — so a finished milestone can never block the next one.
122
128
 
123
129
  ```bash
124
130
  RESUME=$(node .nubos-pilot/bin/np-tools.cjs init resume-work)
125
131
  RESUME_STATUS=$(echo "$RESUME" | node -e "process.stdin.on('data', d => console.log(JSON.parse(d).status))")
126
132
  if [ "$RESUME_STATUS" = "orphan" ]; then
133
+ ORPHAN_ID=$(echo "$RESUME" | node -e "process.stdin.on('data', d => { const p = JSON.parse(d); console.log((p.checkpoint_ids || [])[0] || '') })")
127
134
  CHOICE=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{
128
135
  "type": "select",
129
136
  "header": "Verwaiste Checkpoints gefunden",
130
- "question": "Vor dem Milestone-Start wurden Checkpoint-Dateien ohne passenden STATE.current_task gefunden. Was tun?",
137
+ "question": "Vor dem Milestone-Start wurde ein uncommitteter Checkpoint ohne passenden STATE.current_task gefunden (kein zugehöriger Commit). Was tun?",
131
138
  "options": [
132
139
  {"label": "Clean working tree (reset-slice)", "description": "Verwirft die in-flight Task und löscht ihren Checkpoint."},
133
140
  {"label": "Resume the orphan task", "description": "Setzt STATE.current_task auf den Checkpoint-Eintrag und spawnt den Executor."},
@@ -135,6 +142,13 @@ if [ "$RESUME_STATUS" = "orphan" ]; then
135
142
  ]
136
143
  }')
137
144
  case "$CHOICE" in
145
+ "Clean working tree (reset-slice)")
146
+ node .nubos-pilot/bin/np-tools.cjs reset-slice "$ORPHAN_ID"
147
+ ;;
148
+ "Resume the orphan task")
149
+ echo "execute-phase: resuming orphan task $ORPHAN_ID — run /np:resume-work" >&2
150
+ exit 0
151
+ ;;
138
152
  "Abort") exit 0 ;;
139
153
  esac
140
154
  fi
@@ -419,6 +433,10 @@ for WAVE_INDEX in 0 1 2 ...; do
419
433
  # <verify_excerpt>: tail of $VERIFY_LOG (R≥2 only)
420
434
  # <lang_directive>: $LANG_DIRECTIVE
421
435
  # <skills>: $AGENT_SKILLS_EXECUTOR
436
+ # <economy_mode>: $ECONOMY_MODE — when $ECONOMY_PREVENTION = true (lite/full/
437
+ # ultra) instruct the agent to APPLY the np-executor "Climb the ladder"
438
+ # discipline before writing (prevention-first). When $ECONOMY_MODE = off,
439
+ # instruct it to SKIP the ladder (no economy pressure this run).
422
440
  # RULES — Agent MUST: edit ONLY paths in files_modified (D-04 scope guard) —
423
441
  # success_criteria are the acceptance target, NEVER a licence to touch other files,
424
442
  # run `node np-tools.cjs knowledge-search "<q>" --task $TASK_ID` via Bash
@@ -558,6 +576,12 @@ for WAVE_INDEX in 0 1 2 ...; do
558
576
  # - agents/np-critic-style.md
559
577
  # - agents/np-critic-tests.md
560
578
  # - agents/np-critic-acceptance.md
579
+ # - agents/np-critic-economy.md ← ONLY when $ECONOMY_CRITIC = true (mode full/ultra)
580
+ # (resolved once in the init block via `economy-mode --json`; omit this line
581
+ # entirely when $ECONOMY_CRITIC = false — default mode lite has prevention
582
+ # on but the critic off). When $ECONOMY_MODE = ultra, ALSO append to the
583
+ # prompt: "Economy mode: ultra — lower the shrinkable bar per the Ultra
584
+ # section of np-critic-economy.md." Never inject the module at off/lite.
561
585
  # <report_path>$CRITIC_REPORT_PATH</report_path>
562
586
  # Agent MUST: Write the full findings JSON to $CRITIC_REPORT_PATH,
563
587
  # emit ONLY the verdict-envelope as final message (~150 bytes):
@@ -0,0 +1,93 @@
1
+ ---
2
+ command: np:simplify-debt
3
+ description: Economy-debt ledger — record, list, and resolve simplifications you deferred rather than fixed now, so "later" doesn't become "never". CRUD-only (no agent spawn unless you harvest from a review). Manual twin of the in-loop Economy critic (agents.economy full/ultra).
4
+ argument-hint: "[list [--status open|resolved|all]] | [add --file <f> --line <n> --category <c> --note <text>] | [resolve <id>] | [harvest [<git-range>]]"
5
+ ---
6
+
7
+ # /np:simplify-debt
8
+
9
+ <objective>
10
+ Keep a durable ledger of over-engineering you chose NOT to fix this round. `/np:simplify-review` finds what could be deleted, reused, or condensed; whatever you defer goes here as a tracked entry, so the shortcut is harvested rather than lost. Each entry carries a `file`, optional `line`, one of the four Economy categories (`over-engineering` / `stdlib-reinvention` / `native-duplication` / `shrinkable`), and a one-line note. This command is CRUD over `.nubos-pilot/economy-debt/` — it does not edit source and does not commit code. It is the deferred-work counterpart of the in-loop Economy critic (`/np:execute-phase` with `agents.economy` set to `full` or `ultra`).
11
+ </objective>
12
+
13
+ ## Initialize
14
+
15
+ ```bash
16
+ LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
17
+ VERB="${1:-list}"
18
+ ```
19
+
20
+ `$LANG_DIRECTIVE` governs the prose you wrap around the CLI output; the ledger lines (id, category, `file:line`, note) stay canonical. Supersedes CLAUDE.md.
21
+
22
+ ## Route by verb
23
+
24
+ This is a pure-CRUD workflow — the ledger lives behind `node .nubos-pilot/bin/np-tools.cjs simplify-debt`; never read or write `.nubos-pilot/economy-debt/` directly.
25
+
26
+ ### `list` (default)
27
+
28
+ ```bash
29
+ node .nubos-pilot/bin/np-tools.cjs simplify-debt list --status "${2:-open}"
30
+ ```
31
+
32
+ Prints the open ledger (oldest debt first — longest-deferred is most urgent). `--status resolved` or `--status all` widen the view; append `--json` for the machine shape. Print the output verbatim.
33
+
34
+ ### `add`
35
+
36
+ ```bash
37
+ node .nubos-pilot/bin/np-tools.cjs simplify-debt add \
38
+ --file "$FILE" --line "$LINE" --category "$CATEGORY" --note "$NOTE"
39
+ ```
40
+
41
+ `--category` MUST be one of `over-engineering`, `stdlib-reinvention`, `native-duplication`, `shrinkable` (the CLI rejects anything else). `--line` is optional (omit for a file-level note). Adds are idempotent: re-adding an identical finding is a no-op (`was_new=false`), so re-harvesting the same review never duplicates.
42
+
43
+ ### `resolve`
44
+
45
+ ```bash
46
+ node .nubos-pilot/bin/np-tools.cjs simplify-debt resolve "$ID"
47
+ ```
48
+
49
+ Moves the entry from `open/` to `resolved/` and stamps the resolution time. Use it once the simplification has actually landed (by hand or via a later `/np:execute-phase` round).
50
+
51
+ ### `harvest [<git-range>]`
52
+
53
+ Spawn ONE read-only reviewer — `Agent(subagent_type="np-simplifier", prompt=<…>)` — over the diff (default: working tree + staged vs `HEAD`; or the passed git range). Its prompt MUST carry `agents/np-critic-economy.md` as the rubric (the canonical ladder + safety boundaries) and the captured `git diff --no-color`. For each finding the reviewer returns that you are NOT fixing now, record it with one `simplify-debt add` call (mapping the report tag to the matching `--category`). Findings you fix immediately are not harvested. Print the resulting open ledger when done.
54
+
55
+ ## No Source Mutation
56
+
57
+ CRUD over the ledger only. This workflow NEVER edits source, NEVER stages, NEVER commits code. The ledger files under `.nubos-pilot/economy-debt/` are the only writes; commit them with your normal docs/artifact flow if `workflow.commit_docs` is set.
58
+
59
+ ## Scope Guardrail
60
+
61
+ <scope_guardrail>
62
+ **Do:**
63
+ - Route every ledger read/write through `node .nubos-pilot/bin/np-tools.cjs simplify-debt <verb>` — never touch `.nubos-pilot/economy-debt/` directly.
64
+ - Use one of the four Economy categories on `add`; the CLI is the source of truth and rejects others.
65
+ - On `harvest`, pass `agents/np-critic-economy.md` to the reviewer so the ledger and the in-loop critic apply the identical bar.
66
+
67
+ **Don't:**
68
+ - Edit, stage, or commit source — this command only records deferred work. No `Write`/`Edit` of source, no `git add` of code.
69
+ - Log a test, input-validation, error-handling, security, or required-edge-case removal as debt — those are completeness, never economy (the rubric's safety boundaries; completeness wins).
70
+ - Record low-confidence "could be cleaner" noise — every entry needs a concrete file + replacement, same bar as `/np:simplify-review`.
71
+ </scope_guardrail>
72
+
73
+ ## Output
74
+
75
+ - The ledger view (open / resolved / all) or the result of an `add` / `resolve`, printed verbatim.
76
+ - On `harvest`: one ledger entry per deferred finding, then the refreshed open ledger.
77
+ - No source changes, no code commits.
78
+
79
+ ## Definition of Done
80
+
81
+ Ledger CRUD. Definition of Done, per [`templates/COMPLETENESS.md`](../templates/COMPLETENESS.md):
82
+
83
+ - Rule 3 (Do it with tests) — the ledger NEVER records deleting or weakening test code; coverage is completeness, not debt.
84
+ - Rule 8 (Never present a workaround when the real fix exists) — entries point at the root-cause-simple form, not an obscure golfed one-liner.
85
+ - Rule 11 (Ship the complete thing) — deferred simplifications are tracked, not silently dropped; "later" stays visible until `resolve`.
86
+
87
+ Any violation = the operation is incomplete; surface it and exit non-zero. The orchestrator does not relax these.
88
+
89
+ ## Related Workflows
90
+
91
+ - **`/np:simplify-review <git-range>`** — read-only economy audit of a diff (the finder; this command is the ledger that outlives a single review).
92
+ - **`/np:execute-phase`** — runs the Economy critic in-loop when `agents.economy` is `full` or `ultra`, enforcing the same rubric during execution.
93
+ - **`/np:add-todo`** — general pending-todo capture (this command is the economy-specific deferred-work ledger).
@@ -0,0 +1,103 @@
1
+ ---
2
+ command: np:simplify-review
3
+ description: Read-only economy audit of a git diff, the working tree, or the whole repo (--repo) — flags over-engineering, stdlib-reinvention, native-duplication, and shrinkable logic via the np-simplifier agent. Never edits or commits; emits a deletion-oriented report. Manual twin of the Economy critic axis (agents.economy full/ultra).
4
+ argument-hint: "[<git-range> | --repo] (default: working tree + staged vs HEAD)"
5
+ ---
6
+
7
+ # /np:simplify-review
8
+
9
+ <objective>
10
+ Catalogue what could be deleted, reused, or condensed in a change set — the "wrote-too-much" review. A read-only `np-simplifier` agent scans the diff against the Economy rubric (`agents/np-critic-economy.md`) and emits one finding per removable construct plus a `net: -<N> lines possible.` summary. This command NEVER edits source and NEVER commits; it hands a report to the user. It is the manual counterpart of the in-loop Economy critic (`/np:execute-phase` with `agents.economy` set to `full` or `ultra`), and both apply the identical rubric and safety boundaries.
11
+ </objective>
12
+
13
+ ## Initialize
14
+
15
+ ```bash
16
+ LANG_DIRECTIVE=$(node .nubos-pilot/bin/np-tools.cjs lang-directive)
17
+ RANGE="$*"
18
+ if [[ "$RANGE" == "--repo" || "$RANGE" == "--all" ]]; then
19
+ SCOPE_MODE="repo"
20
+ FILES=$(git ls-files)
21
+ SCOPE_DESC="whole repository (tracked files)"
22
+ if [[ -z "$FILES" ]]; then
23
+ echo "No tracked files in scope ($SCOPE_DESC). Nothing to review."
24
+ exit 0
25
+ fi
26
+ elif [[ -n "$RANGE" ]]; then
27
+ SCOPE_MODE="diff"
28
+ DIFF=$(git diff --no-color "$RANGE")
29
+ SCOPE_DESC="$RANGE"
30
+ else
31
+ SCOPE_MODE="diff"
32
+ DIFF=$(git diff --no-color HEAD)
33
+ SCOPE_DESC="working tree + staged vs HEAD"
34
+ fi
35
+ if [[ "$SCOPE_MODE" == "diff" && -z "$DIFF" ]]; then
36
+ echo "No changes in scope ($SCOPE_DESC). Nothing to review."
37
+ exit 0
38
+ fi
39
+ ```
40
+
41
+ Capture is read-only — neither `git diff` nor `git ls-files` stages, edits, or commits anything. There are two scope modes:
42
+
43
+ - **diff** (default) — a range (`HEAD~5..HEAD`, a branch name, `--staged`, …) is passed through verbatim; with no argument the scope is uncommitted + staged work against `HEAD`. This is the "review what just changed" mode.
44
+ - **repo** (`--repo` / `--all`) — audits the whole tracked tree, not just a diff. `git ls-files` hands the agent the file roster; the agent walks the existing source for standing over-engineering (single-use abstractions, hand-rolled stdlib, duplicated native features, condensable logic) that predates any one change. Slower and noisier than diff mode — use it for a periodic cleanup pass, not every review.
45
+
46
+ **Language (SSOT = `.nubos-pilot/config.json` → `response_language`).** `$LANG_DIRECTIVE` is authoritative for the report's prose and the final summary line. Finding lines (`<file>:L<line>: <tag> …`), file paths, and code snippets stay canonical. Supersedes CLAUDE.md.
47
+
48
+ ## Review
49
+
50
+ Spawn ONE read-only reviewer — `Agent(subagent_type="np-simplifier", prompt=<…>)`, sonnet by default. The prompt MUST carry:
51
+
52
+ - `<files_to_read>` listing `agents/np-critic-economy.md` (the canonical rubric — ladder, categories, severity bar, safety boundaries), plus `.nubos-pilot/codebase/INDEX.md` and `.nubos-pilot/RULES.md` when present (stdlib / native-feature / existing-helper context).
53
+ - The review scope, by mode:
54
+ - **diff mode** — pass the captured `$DIFF` as the scope; the agent reviews only the changed hunks.
55
+ - **repo mode** — pass `$SCOPE_DESC` plus the `$FILES` roster from `git ls-files`, and instruct the agent to walk the tracked source itself (`Read`/`Grep`/`Glob`) under the same rubric. The agent should skip vendored, generated, and lock files and prioritise the largest hand-written modules. Because there is no diff, findings cite the source line as `<file>:L<line>` from the file it read.
56
+ - `$SCOPE_MODE` and `$SCOPE_DESC` so the agent knows whether it is auditing a change set or the standing codebase.
57
+ - `$LANG_DIRECTIVE` so the prose follows the project language.
58
+
59
+ The agent is READ-ONLY (`tools: Read, Bash, Grep, Glob` — no Write/Edit). It returns a plain-text report: one line per finding in the shape `<file>:L<line>: <tag> <what>. <replacement>.` (tags `delete:` / `stdlib:` / `native:` / `shrink:`), ending with `net: -<N> lines possible.` or `Lean already. Ship.`
60
+
61
+ ## Report
62
+
63
+ Print the agent's report verbatim to the user. Do not edit files, do not stage, do not commit — this command only catalogues. Close with the next-step hint:
64
+
65
+ ```
66
+ Reductions are suggestions, not applied. To act on them: edit by hand, or run
67
+ /np:execute-phase with agents.economy set to full (or ultra) so the Economy critic
68
+ enforces the same bar inside the adversarial loop.
69
+ ```
70
+
71
+ ## Scope Guardrail
72
+
73
+ <scope_guardrail>
74
+ **Do:**
75
+ - Capture the scope read-only — `git diff` in diff mode, `git ls-files` in `--repo` mode (no staging, no `-w` rewrites, no commit).
76
+ - Spawn exactly one `np-simplifier` agent; pass it `agents/np-critic-economy.md` as the rubric so the manual command and the in-loop critic never diverge.
77
+ - Print the report verbatim; respect `$LANG_DIRECTIVE` for prose only.
78
+
79
+ **Don't:**
80
+ - Edit, stage, or commit anything — this is a read-only audit. No `git add`, no `commit`, no `Write`/`Edit` of source.
81
+ - Flag tests, input validation, error handling, security/access-control, or required edge cases as removable (the rubric's safety boundaries; completeness wins over economy).
82
+ - Emit low-confidence "could be cleaner" noise — high-confidence, concrete replacements only.
83
+ </scope_guardrail>
84
+
85
+ ## Output
86
+
87
+ - A plain-text economy report to the user (findings + `net: -<N> lines possible.` or `Lean already. Ship.`).
88
+ - No filesystem changes, no commits, no state mutation — read-only by contract.
89
+
90
+ ## Definition of Done
91
+
92
+ This workflow audits a diff for economy and reports. The Definition of Done, per [`templates/COMPLETENESS.md`](../templates/COMPLETENESS.md):
93
+
94
+ - Rule 3 (Do it with tests) — the report NEVER proposes deleting or weakening test code; coverage is completeness, not bloat.
95
+ - Rule 5 (Aim to genuinely impress) — every finding cites file, line, the exact construct, and a concrete replacement; no vague "could be simpler" entries.
96
+ - Rule 8 (Never present a workaround when the real fix exists) — reductions favour the root-cause-simple form over obscure golfed one-liners.
97
+
98
+ Any violation = the review is incomplete; surface it and exit non-zero. The orchestrator does not relax these.
99
+
100
+ ## Related Workflows
101
+
102
+ - **`/np:verify-work <N>`** — correctness/acceptance verification (the orthogonal axis; economy never judges correctness).
103
+ - **`/np:execute-phase`** — runs the Economy critic in-loop when `agents.economy` is `full` or `ultra`, enforcing this exact rubric during execution.