nubos-pilot 0.6.0 → 0.6.2
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/agents/np-sc-extractor.md +85 -0
- package/bin/install.js +83 -0
- package/bin/np-tools/_commands.cjs +1 -0
- package/bin/np-tools/discuss-phase.test.cjs +3 -3
- package/bin/np-tools/text-mode.test.cjs +9 -6
- package/bin/np-tools/update-phase-meta.cjs +77 -0
- package/bin/np-tools/update-phase-meta.test.cjs +132 -0
- package/lib/agents.test.cjs +1 -0
- package/lib/install/claude-hooks.cjs +195 -0
- package/lib/install/claude-hooks.test.cjs +163 -0
- package/lib/roadmap.cjs +107 -0
- package/lib/roadmap.test.cjs +84 -0
- package/lib/text-mode.cjs +1 -1
- package/lib/text-mode.test.cjs +11 -22
- package/np-tools.cjs +1 -0
- package/package.json +1 -1
- package/templates/claude/payload/hooks/np-ctx-monitor.js +94 -0
- package/templates/claude/payload/hooks/np-statusline.js +140 -0
- package/workflows/add-todo.md +4 -4
- package/workflows/discuss-phase.md +76 -44
- package/workflows/discuss-project.md +5 -0
- package/workflows/execute-phase.md +4 -5
- package/workflows/new-milestone.md +4 -5
- package/workflows/new-project.md +4 -5
- package/workflows/note.md +5 -5
- package/workflows/plan-phase.md +27 -4
- package/workflows/propose-milestones.md +4 -1
- package/workflows/research-phase.md +4 -4
- package/workflows/resume-work.md +4 -4
- package/workflows/session-report.md +4 -4
- package/workflows/validate-phase.md +4 -4
- package/workflows/verify-work.md +4 -5
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
|
|
8
|
+
const BAR_WIDTH = 10;
|
|
9
|
+
const AUTOCOMPACT_BUFFER = 0.835;
|
|
10
|
+
const DEFAULT_WINDOW = 200_000;
|
|
11
|
+
const EXTENDED_WINDOW = 1_000_000;
|
|
12
|
+
|
|
13
|
+
function readStdinJson() {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
if (process.stdin.isTTY) return resolve({});
|
|
16
|
+
let buf = '';
|
|
17
|
+
process.stdin.setEncoding('utf-8');
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
try { process.stdin.removeAllListeners(); } catch {}
|
|
20
|
+
resolve(safeParse(buf));
|
|
21
|
+
}, 500);
|
|
22
|
+
process.stdin.on('data', (chunk) => { buf += chunk; });
|
|
23
|
+
process.stdin.on('end', () => { clearTimeout(timer); resolve(safeParse(buf)); });
|
|
24
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(safeParse(buf)); });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function safeParse(s) {
|
|
29
|
+
try { return s ? JSON.parse(s) : {}; } catch { return {}; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function modelWindow(payload) {
|
|
33
|
+
const id = String((payload && payload.model && payload.model.id) || '');
|
|
34
|
+
const name = String((payload && payload.model && payload.model.display_name) || '');
|
|
35
|
+
const s = (id + ' ' + name).toLowerCase();
|
|
36
|
+
if (s.includes('[1m]') || /\b1m\b/.test(s) || s.includes('1-m') || s.includes('1000k')) {
|
|
37
|
+
return EXTENDED_WINDOW;
|
|
38
|
+
}
|
|
39
|
+
return DEFAULT_WINDOW;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function lastUsage(transcriptPath) {
|
|
43
|
+
if (!transcriptPath) return null;
|
|
44
|
+
let raw;
|
|
45
|
+
try { raw = fs.readFileSync(transcriptPath, 'utf-8'); } catch { return null; }
|
|
46
|
+
const lines = raw.split('\n');
|
|
47
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
48
|
+
const line = lines[i];
|
|
49
|
+
if (!line) continue;
|
|
50
|
+
let obj;
|
|
51
|
+
try { obj = JSON.parse(line); } catch { continue; }
|
|
52
|
+
const usage = obj && obj.message && obj.message.usage;
|
|
53
|
+
if (!usage || typeof usage !== 'object') continue;
|
|
54
|
+
const input = Number(usage.input_tokens || 0);
|
|
55
|
+
const cacheCreation = Number(usage.cache_creation_input_tokens || 0);
|
|
56
|
+
const cacheRead = Number(usage.cache_read_input_tokens || 0);
|
|
57
|
+
const output = Number(usage.output_tokens || 0);
|
|
58
|
+
const total = input + cacheCreation + cacheRead + output;
|
|
59
|
+
if (!Number.isFinite(total) || total <= 0) continue;
|
|
60
|
+
return { input, cacheCreation, cacheRead, output, total };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderBar(used, limit) {
|
|
66
|
+
const fraction = Math.max(0, Math.min(1, used / limit));
|
|
67
|
+
const ofUsable = Math.max(0, Math.min(1, fraction / AUTOCOMPACT_BUFFER));
|
|
68
|
+
const pct = Math.round(ofUsable * 100);
|
|
69
|
+
const filled = Math.round(ofUsable * BAR_WIDTH);
|
|
70
|
+
const bar = '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
|
|
71
|
+
let color = '\x1b[32m';
|
|
72
|
+
let suffix = '';
|
|
73
|
+
if (pct >= 80) { color = '\x1b[31m'; suffix = ' 💀'; }
|
|
74
|
+
else if (pct >= 65) { color = '\x1b[38;5;208m'; }
|
|
75
|
+
else if (pct >= 50) { color = '\x1b[33m'; }
|
|
76
|
+
return color + bar + '\x1b[0m ' + pct + '%' + suffix;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function terminalWidth() {
|
|
80
|
+
const envCols = Number(process.env.COLUMNS);
|
|
81
|
+
if (Number.isFinite(envCols) && envCols > 0) return envCols;
|
|
82
|
+
if (process.stdout && process.stdout.columns) return process.stdout.columns;
|
|
83
|
+
try {
|
|
84
|
+
const { execSync } = require('node:child_process');
|
|
85
|
+
const out = execSync('tput cols', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
86
|
+
const n = Number(out);
|
|
87
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
88
|
+
} catch {}
|
|
89
|
+
return 120;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function visibleLen(s) {
|
|
93
|
+
const stripped = s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
94
|
+
let w = 0;
|
|
95
|
+
for (const ch of stripped) {
|
|
96
|
+
const cp = ch.codePointAt(0);
|
|
97
|
+
if (cp > 0xFFFF) w += 2;
|
|
98
|
+
else w += 1;
|
|
99
|
+
}
|
|
100
|
+
return w;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function centerLine(line) {
|
|
104
|
+
const width = terminalWidth();
|
|
105
|
+
const pad = Math.max(0, Math.floor((width - visibleLen(line)) / 2));
|
|
106
|
+
return ' '.repeat(pad) + line;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeBridge(payload, usage, limit) {
|
|
110
|
+
const sid = payload && payload.session_id;
|
|
111
|
+
if (!sid) return;
|
|
112
|
+
const bridgePath = path.join(os.tmpdir(), 'claude-ctx-' + String(sid).replace(/[^a-zA-Z0-9._-]/g, '_') + '.json');
|
|
113
|
+
const data = {
|
|
114
|
+
session_id: sid,
|
|
115
|
+
used: usage.total,
|
|
116
|
+
limit: limit,
|
|
117
|
+
usable_remaining_pct: Math.max(0, Math.round(((AUTOCOMPACT_BUFFER * limit) - usage.total) / (AUTOCOMPACT_BUFFER * limit) * 100)),
|
|
118
|
+
updated_at: new Date().toISOString(),
|
|
119
|
+
};
|
|
120
|
+
try { fs.writeFileSync(bridgePath, JSON.stringify(data)); } catch {}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
(async () => {
|
|
124
|
+
let payload = {};
|
|
125
|
+
try { payload = await readStdinJson(); } catch { payload = {}; }
|
|
126
|
+
const limit = modelWindow(payload);
|
|
127
|
+
const usage = lastUsage(payload && payload.transcript_path);
|
|
128
|
+
const prefix = '\x1b[38;5;33mnubos-pilot\x1b[0m';
|
|
129
|
+
if (!usage) {
|
|
130
|
+
process.stdout.write(centerLine(prefix));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
writeBridge(payload, usage, limit);
|
|
134
|
+
const bar = renderBar(usage.total, limit);
|
|
135
|
+
const modelName = (payload && payload.model && payload.model.display_name) || '';
|
|
136
|
+
const tail = modelName ? ' \x1b[2m' + modelName + '\x1b[0m' : '';
|
|
137
|
+
process.stdout.write(centerLine(prefix + ' ctx ' + bar + tail));
|
|
138
|
+
})().catch(() => {
|
|
139
|
+
process.stdout.write('\x1b[38;5;33mnubos-pilot\x1b[0m');
|
|
140
|
+
});
|
package/workflows/add-todo.md
CHANGED
|
@@ -47,10 +47,10 @@ through `lib/layout.cjs.slugify` (strips to `[a-z0-9-]` only;
|
|
|
47
47
|
filename-injection mitigation) and validates the description length
|
|
48
48
|
(<= 500 chars) before any filesystem write occurs.
|
|
49
49
|
|
|
50
|
-
**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
51
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
52
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
53
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
54
54
|
|
|
55
55
|
## Create Pending Dir
|
|
56
56
|
|
|
@@ -34,22 +34,20 @@ Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`,
|
|
|
34
34
|
`milestone_context_path`, `has_context`, `has_milestone_dir`, `goal`,
|
|
35
35
|
`requirements`, `agent_skills`, `mode`, `text_mode`, `text_mode_source`.
|
|
36
36
|
|
|
37
|
-
**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- `
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rest of the workflow (validation, canonical-ref accumulation, template
|
|
52
|
-
render, commit) is unchanged.
|
|
37
|
+
**Askuser routing (SSOT = INIT payload).** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a **spec**, not a literal command. Pick the path once at Initialize:
|
|
38
|
+
|
|
39
|
+
- **Claude Code runtime** (you are running inside Claude Code — the `AskUserQuestion` tool is available to you): **do not** shell out to `np-tools.cjs askuser`. Parse the JSON spec inside each askuser block and call the native `AskUserQuestion` tool directly with one question entry:
|
|
40
|
+
- `type: "select"` → `{ question, header, multiSelect: false, options: [{label, description}...] }`
|
|
41
|
+
- `type: "multiselect"` → `{ question, header, multiSelect: true, options: [{label, description}...] }`
|
|
42
|
+
- `type: "confirm"` → single question with `options: [{label: "Yes"}, {label: "No"}]`, `multiSelect: false`
|
|
43
|
+
- `type: "input"` → ask as a plain free-form question in the chat; the user replies inline
|
|
44
|
+
Use a short `header` (≤12 chars) that labels the category, e.g. `"Discuss"`, `"Scope"`, `"Overwrite?"`. This is the default path and gives the user a real selection menu.
|
|
45
|
+
|
|
46
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render every question inline as a plain-text numbered list; the user replies with the number. This path is opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
47
|
+
|
|
48
|
+
- **Other runtime with TTY** (Codex, Gemini, …): run the shell `node .nubos-pilot/bin/np-tools.cjs askuser --json '…'` block verbatim.
|
|
49
|
+
|
|
50
|
+
`text_mode_source` in the INIT payload (`config` / `default`) is informational only — it does not change the routing above.
|
|
53
51
|
|
|
54
52
|
If the user passed `--assumptions`, route to
|
|
55
53
|
`workflows/discuss-phase-assumptions.md` and exit this workflow.
|
|
@@ -142,33 +140,19 @@ Capture the idea in a "Deferred Ideas" section. Don't lose it, don't act on it.
|
|
|
142
140
|
## Answer Validation
|
|
143
141
|
|
|
144
142
|
<answer_validation>
|
|
145
|
-
**Routing
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
stderr JSON with `"code":"askuser-no-tty"`), that means the runtime
|
|
154
|
-
detection missed something; **skip retry** and treat the remainder of the
|
|
155
|
-
workflow as text-mode (plain-text numbered lists).
|
|
156
|
-
2. If the response is empty or whitespace-only (exit 0 but no value), retry
|
|
157
|
-
the question once with the same parameters.
|
|
158
|
-
3. If still empty, present the options as a plain-text numbered list and ask
|
|
159
|
-
the user to type their choice number.
|
|
143
|
+
**Routing was decided at Initialize** (see "Askuser routing" section above). This section documents per-prompt validation only.
|
|
144
|
+
|
|
145
|
+
**Claude Code path (`AskUserQuestion` tool):** the tool guarantees a non-empty selection; no validation needed.
|
|
146
|
+
|
|
147
|
+
**Shell askuser path (other runtimes with TTY):**
|
|
148
|
+
1. If `askuser` exits with structured error `askuser-no-tty` (exit code 1, stderr JSON with `"code":"askuser-no-tty"`), that means the runtime detection missed something; **skip retry** and treat the remainder of the workflow as text-mode (plain-text numbered lists).
|
|
149
|
+
2. If the response is empty or whitespace-only (exit 0 but no value), retry the question once with the same parameters.
|
|
150
|
+
3. If still empty, present the options as a plain-text numbered list and ask the user to type their choice number.
|
|
160
151
|
Never proceed with an empty answer.
|
|
161
152
|
|
|
162
|
-
**
|
|
163
|
-
- Auto-detected: any Claude Code session (`CLAUDECODE=1` /
|
|
164
|
-
`CLAUDE_CODE_ENTRYPOINT` set) — default behavior, no user action needed.
|
|
165
|
-
- Opt-in per project: set `workflow.text_mode: true` in
|
|
166
|
-
`.nubos-pilot/config.json`.
|
|
167
|
-
- Opt-out per project: set `workflow.text_mode: false` in
|
|
168
|
-
`.nubos-pilot/config.json` (overrides runtime detection).
|
|
153
|
+
**Text-mode (numbered-list path):** user reply must parse as a valid index (1-N) for select/multiselect, `y/n` for confirm, or any non-empty string for input. Re-ask on invalid input.
|
|
169
154
|
|
|
170
|
-
|
|
171
|
-
payload, not just discuss-phase.
|
|
155
|
+
**Enable text mode** (force the numbered-list path regardless of runtime): set `workflow.text_mode: true` in `.nubos-pilot/config.json`. Useful for remote-control setups or runtimes where neither `AskUserQuestion` nor TTY stdin are reliable.
|
|
172
156
|
</answer_validation>
|
|
173
157
|
|
|
174
158
|
## Process
|
|
@@ -386,18 +370,66 @@ If the template lacks a key, `render()` throws
|
|
|
386
370
|
`NubosPilotError('template-missing-key', …)` — the workflow must not swallow
|
|
387
371
|
that error. Fix the template or the accumulator, don't mask the failure.
|
|
388
372
|
|
|
373
|
+
### Step 6b: Extract + persist Success Criteria (np-sc-extractor)
|
|
374
|
+
|
|
375
|
+
CONTEXT.md now captures the decisions. Success Criteria in `roadmap.yaml` are still empty for this milestone — downstream `/np:verify-work` reads them from there, so we must persist them now. Spawn the SC-extractor (haiku) to derive observable SCs from goal + requirements + CONTEXT.md + any pre-existing `M<NNN>-ROADMAP.md` / `M<NNN>-META.json` sidecars, and call `update-phase-meta` to write them.
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
SC_START=$(node .nubos-pilot/bin/np-tools.cjs metrics start-timestamp)
|
|
379
|
+
SC_MODEL=$(node .nubos-pilot/bin/np-tools.cjs resolve-model np-sc-extractor --profile balanced)
|
|
380
|
+
|
|
381
|
+
REQS_PATH=".nubos-pilot/REQUIREMENTS.md"
|
|
382
|
+
[[ -f "$REQS_PATH" ]] || REQS_PATH=".planning/REQUIREMENTS.md"
|
|
383
|
+
|
|
384
|
+
EXISTING_SC_JSON=$(node -e '
|
|
385
|
+
const r = require("./lib/roadmap.cjs");
|
|
386
|
+
const p = r.getPhase(process.argv[1]);
|
|
387
|
+
process.stdout.write(JSON.stringify(p.success_criteria || []));
|
|
388
|
+
' "$PHASE")
|
|
389
|
+
|
|
390
|
+
# Spawn agent=np-sc-extractor tier=haiku model=$SC_MODEL milestone=$PHASE
|
|
391
|
+
# input: milestone=$PHASE, milestone_id=$MILESTONE_ID, milestone_dir=$MILESTONE_DIR,
|
|
392
|
+
# context_path=$CONTEXT_PATH, requirements_path=$REQS_PATH,
|
|
393
|
+
# existing_success_criteria=$EXISTING_SC_JSON
|
|
394
|
+
# output: calls `np-tools.cjs update-phase-meta $PHASE --stdin` with
|
|
395
|
+
# {"success_criteria": [{id:"SC-N", text:"..."}, ...]} and prints summary.
|
|
396
|
+
|
|
397
|
+
SC_END=$(node .nubos-pilot/bin/np-tools.cjs metrics end-timestamp)
|
|
398
|
+
node .nubos-pilot/bin/np-tools.cjs metrics record \
|
|
399
|
+
--agent np-sc-extractor --tier haiku --resolved-model "$SC_MODEL" \
|
|
400
|
+
--phase "$PHASE" --plan "${MILESTONE_ID}-sc" --task "${MILESTONE_ID}-sc-extract" \
|
|
401
|
+
--started "$SC_START" --ended "$SC_END" \
|
|
402
|
+
--tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
|
|
403
|
+
--retry-count 0 --status ok --runtime "$RUNTIME"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
After the spawn, sanity-check that `success_criteria` is non-empty:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
SC_COUNT=$(node -e '
|
|
410
|
+
const r = require("./lib/roadmap.cjs");
|
|
411
|
+
const p = r.getPhase(process.argv[1]);
|
|
412
|
+
process.stdout.write(String((p.success_criteria || []).length));
|
|
413
|
+
' "$PHASE")
|
|
414
|
+
if [[ "$SC_COUNT" -lt 1 ]]; then
|
|
415
|
+
echo "ERROR: np-sc-extractor produced no success_criteria for $MILESTONE_ID — refusing to continue." >&2
|
|
416
|
+
exit 1
|
|
417
|
+
fi
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
A failure here is loud by design: `/np:verify-work` and `/np:validate-phase` depend on a populated `success_criteria[]`. If the extractor cannot derive any, fix the goal/requirements/CONTEXT.md inputs before retrying.
|
|
421
|
+
|
|
389
422
|
### Step 7: Commit respecting config.commit_docs
|
|
390
423
|
|
|
391
424
|
```bash
|
|
392
425
|
COMMIT_DOCS=$(node .nubos-pilot/bin/np-tools.cjs config-get workflow.commit_docs 2>/dev/null || echo "true")
|
|
393
426
|
if [[ "$COMMIT_DOCS" == "true" ]]; then
|
|
394
|
-
git add "$CONTEXT_PATH"
|
|
395
|
-
git commit -m "docs($MILESTONE_ID): capture milestone context"
|
|
427
|
+
git add "$CONTEXT_PATH" .nubos-pilot/roadmap.yaml .nubos-pilot/ROADMAP.md
|
|
428
|
+
git commit -m "docs($MILESTONE_ID): capture milestone context + success criteria"
|
|
396
429
|
fi
|
|
397
430
|
```
|
|
398
431
|
|
|
399
|
-
If `workflow.commit_docs` is false, leave the
|
|
400
|
-
opting into manual commit gating.
|
|
432
|
+
If `workflow.commit_docs` is false, leave both CONTEXT.md and the roadmap edits uncommitted — the user is opting into manual commit gating.
|
|
401
433
|
|
|
402
434
|
### Step 8: Confirm and next steps
|
|
403
435
|
|
|
@@ -61,6 +61,11 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
|
61
61
|
narrative status updates, and the prose written into PROJECT.md sections.
|
|
62
62
|
Supersedes CLAUDE.md managed block.
|
|
63
63
|
|
|
64
|
+
**Askuser routing.** The "Use `np-tools.cjs askuser` for every prompt" rule below is SC-5 gateway enforcement — the JSON spec must pass through np-tools for logging/validation. Pick the presentation path:
|
|
65
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
66
|
+
- **`text_mode == true`** (INIT payload): skip shell askuser calls and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
67
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute `node .nubos-pilot/bin/np-tools.cjs askuser --json '…'` directly.
|
|
68
|
+
|
|
64
69
|
Parse: `mode`, `sub_mode` (`bootstrap` or `refresh`), `project_md_exists`,
|
|
65
70
|
`scan_context`, `questions[]`, `required_fields[]`.
|
|
66
71
|
|
|
@@ -32,11 +32,10 @@ directive in CLAUDE.md managed block.
|
|
|
32
32
|
|
|
33
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`, `text_mode`, `text_mode_source`, `agent_skills`.
|
|
34
34
|
|
|
35
|
-
**
|
|
36
|
-
|
|
37
|
-
and render
|
|
38
|
-
|
|
39
|
-
`.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
35
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below (including the orphan-checkpoint and empty-milestone prompts) is a spec, not a literal command. Pick the path once at Initialize:
|
|
36
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
37
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
38
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
40
39
|
|
|
41
40
|
`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`).
|
|
42
41
|
|
|
@@ -62,11 +62,10 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
|
62
62
|
user-facing output, and any prose written into milestone artefacts (YAML
|
|
63
63
|
keys, IDs, and identifiers stay canonical English). Supersedes CLAUDE.md.
|
|
64
64
|
|
|
65
|
-
**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
`workflow.text_mode`.
|
|
65
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
66
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
67
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
68
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
70
69
|
|
|
71
70
|
Payload: three questions — `milestone_name`, `milestone_goal`, `create_req_prefix` (confirm).
|
|
72
71
|
|
package/workflows/new-project.md
CHANGED
|
@@ -126,11 +126,10 @@ user-facing output, and any narrative prose written into PROJECT.md /
|
|
|
126
126
|
REQUIREMENTS.md (field names and YAML keys stay canonical English).
|
|
127
127
|
Supersedes CLAUDE.md.
|
|
128
128
|
|
|
129
|
-
**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
129
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
130
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
131
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
132
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
134
133
|
|
|
135
134
|
```bash
|
|
136
135
|
ANS_PROJECT_NAME=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{"type":"input","prompt":"Project name?"}')
|
package/workflows/note.md
CHANGED
|
@@ -52,16 +52,16 @@ into `$TEXT`. Empty text after stripping is an error — there is no
|
|
|
52
52
|
`list` or `promote` subcommand here (deferred to a future
|
|
53
53
|
capture-management plan).
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Askuser routing.** Resolve once at the start:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
TEXT_MODE=$(node .nubos-pilot/bin/np-tools.cjs text-mode 2>/dev/null || echo false)
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`
|
|
61
|
+
Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path:
|
|
62
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
63
|
+
- **`$TEXT_MODE == "true"`**: skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
64
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
65
65
|
|
|
66
66
|
## Compute Paths
|
|
67
67
|
|
package/workflows/plan-phase.md
CHANGED
|
@@ -88,10 +88,10 @@ prompts as a system-level rule. This supersedes any directive in CLAUDE.md.
|
|
|
88
88
|
|
|
89
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`, `text_mode`, `text_mode_source`, `agent_skills`.
|
|
90
90
|
|
|
91
|
-
**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
92
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
93
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
94
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
95
95
|
|
|
96
96
|
`PLAN_ID` and `TASK_ID` default to `${milestone_id}-plan` / `${milestone_id}-planner-run` for the metrics records.
|
|
97
97
|
|
|
@@ -118,6 +118,29 @@ case "$CHOICE" in
|
|
|
118
118
|
esac
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
### Gate 1b — Empty success_criteria
|
|
122
|
+
|
|
123
|
+
If `success_criteria.length == 0`:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
CHOICE=$(node .nubos-pilot/bin/np-tools.cjs askuser --json '{
|
|
127
|
+
"type": "select",
|
|
128
|
+
"header": "No SCs in roadmap.yaml",
|
|
129
|
+
"question": "Milestone has no success_criteria in roadmap.yaml. Downstream /np:verify-work will produce an empty VERIFICATION.md. How to proceed?",
|
|
130
|
+
"options": [
|
|
131
|
+
{"label": "Run /np:discuss-phase first", "description": "Recommended — np-sc-extractor derives SCs from CONTEXT.md + goal + requirements and writes them to roadmap.yaml."},
|
|
132
|
+
{"label": "Continue anyway", "description": "Plan the milestone without SCs; you must back-fill them before /np:verify-work."},
|
|
133
|
+
{"label": "Abort", "description": "Exit without changes."}
|
|
134
|
+
]
|
|
135
|
+
}')
|
|
136
|
+
case "$CHOICE" in
|
|
137
|
+
"Run /np:discuss-phase"*) echo "Run: /np:discuss-phase $PHASE"; exit 0 ;;
|
|
138
|
+
"Abort") exit 0 ;;
|
|
139
|
+
esac
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The planner will still emit a plan without SCs, but you are consciously opting into a known-broken verify-work path. The safer default is always to re-run `/np:discuss-phase` — Step 6b there spawns `np-sc-extractor` which populates `roadmap.yaml` directly.
|
|
143
|
+
|
|
121
144
|
### Gate 2 — Missing slice RESEARCH.md
|
|
122
145
|
|
|
123
146
|
Research is per-slice (`slices/S<NNN>/S<NNN>-RESEARCH.md`). The planner can plan without research, but if the roadmap config requires it, ask. The `--research` flag auto-dispatches `/np:research-phase` before re-entering.
|
|
@@ -70,7 +70,10 @@ if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
|
70
70
|
**Language (SSOT = `.nubos-pilot/config.json` → `response_language`).**
|
|
71
71
|
`$LANG_DIRECTIVE` is authoritative. Obey it for askuser prompt texts, AI-facing reasoning shown to the user, and any narrative prose. YAML keys, milestone IDs, and status strings stay canonical English.
|
|
72
72
|
|
|
73
|
-
**
|
|
73
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
74
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
75
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
76
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
74
77
|
|
|
75
78
|
Parse INIT for: `milestones[]` (each with `id`, `name`, `goal`, `status`, `classification`, `slice_count`, `context`, `touchable`, `modification_requires_confirm`), `project_md`, `requirements_md`, `project_has_tbd`, `current_state_milestone`, `next_milestone_number`, `guidance`.
|
|
76
79
|
|
|
@@ -107,10 +107,10 @@ project language. This supersedes CLAUDE.md.
|
|
|
107
107
|
`RUNTIME` is resolved once here and reused by the metrics-record call at the
|
|
108
108
|
researcher spawn site (Step 4) per D-06 workflow-writer pattern.
|
|
109
109
|
|
|
110
|
-
**
|
|
111
|
-
|
|
112
|
-
plain-text numbered lists in
|
|
113
|
-
(
|
|
110
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
111
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
112
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
113
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
114
114
|
|
|
115
115
|
The payload shape:
|
|
116
116
|
|
package/workflows/resume-work.md
CHANGED
|
@@ -24,10 +24,10 @@ askuser prompts. When spawning the np-executor to continue a checkpoint,
|
|
|
24
24
|
pass `$LANG_DIRECTIVE` into the spawn prompt so resumed task summaries
|
|
25
25
|
follow the project language. Supersedes CLAUDE.md.
|
|
26
26
|
|
|
27
|
-
**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
28
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
29
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
30
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
31
31
|
|
|
32
32
|
## Execution
|
|
33
33
|
|
|
@@ -62,10 +62,10 @@ canonical English. Supersedes CLAUDE.md.
|
|
|
62
62
|
TEXT_MODE=$(node .nubos-pilot/bin/np-tools.cjs text-mode 2>/dev/null || echo false)
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`
|
|
65
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
66
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
67
|
+
- **`$TEXT_MODE == "true"`** (from the check above, or INIT payload `text_mode == true`): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
68
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
69
69
|
|
|
70
70
|
The filename format is `YYYY-MM-DDTHHMM-session-report.md` (D-17 —
|
|
71
71
|
4-char HHMM, no seconds, local time) so reports sort
|
|
@@ -33,10 +33,10 @@ field names stay English. Supersedes CLAUDE.md.
|
|
|
33
33
|
|
|
34
34
|
Parse JSON for: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`, `slice_uat`, `text_mode`, `text_mode_source`.
|
|
35
35
|
|
|
36
|
-
**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below is a spec, not a literal command. Pick the path once at Initialize:
|
|
37
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
38
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
39
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
MILESTONE_ID=$(echo "$INIT" | jq -r '.milestone_id')
|
package/workflows/verify-work.md
CHANGED
|
@@ -31,11 +31,10 @@ CLAUDE.md.
|
|
|
31
31
|
|
|
32
32
|
Parse: `milestone`, `milestone_id`, `milestone_dir`, `milestone_name`, `success_criteria`, `draft_results`, `verification_path`, `slice_uat`, `verifier_tier`, `text_mode`, `text_mode_source`, `agent_skills`.
|
|
33
33
|
|
|
34
|
-
**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`workflow.text_mode`.
|
|
34
|
+
**Askuser routing.** Every `node .nubos-pilot/bin/np-tools.cjs askuser …` block below (including the Pass-2 `needs_user_confirm` gate) is a spec, not a literal command. Pick the path once at Initialize:
|
|
35
|
+
- **Claude Code** (native `AskUserQuestion` tool is available): parse the JSON spec and call `AskUserQuestion` directly. `select` → `multiSelect: false`; `multiselect` → `multiSelect: true`; `confirm` → `options: [{label: "Yes"}, {label: "No"}]`; `input` → ask free-form in chat. Use a short `header` (≤12 chars).
|
|
36
|
+
- **`text_mode == true`** (INIT payload): skip every askuser block and render questions as plain-text numbered lists. Opt-in via `.nubos-pilot/config.json` → `workflow.text_mode`.
|
|
37
|
+
- **Other runtime with TTY** (Codex, Gemini, …): execute the shell `askuser` block verbatim.
|
|
39
38
|
|
|
40
39
|
## Pass 1 — verifier agent
|
|
41
40
|
|