nubos-pilot 1.1.3 → 1.2.1
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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +30 -1
- package/SECURITY.md +60 -0
- package/agents/np-executor.md +20 -0
- package/agents/np-security-reviewer.md +49 -3
- package/bin/install.js +111 -41
- package/bin/np-tools/_args.cjs +8 -2
- package/bin/np-tools/_commands.cjs +1 -0
- package/bin/np-tools/_memory-resolve.cjs +4 -4
- package/bin/np-tools/checkpoint.cjs +1 -1
- package/bin/np-tools/close-project.cjs +3 -29
- package/bin/np-tools/commit-task.cjs +31 -35
- package/bin/np-tools/commit.cjs +0 -3
- package/bin/np-tools/config.cjs +4 -13
- package/bin/np-tools/discuss-phase.cjs +4 -27
- package/bin/np-tools/doctor.cjs +76 -16
- package/bin/np-tools/doctor.test.cjs +14 -0
- package/bin/np-tools/execute-milestone.cjs +6 -27
- package/bin/np-tools/handoff-write.cjs +16 -2
- package/bin/np-tools/init-dispatch.test.cjs +21 -0
- package/bin/np-tools/knowledge-search.cjs +0 -3
- package/bin/np-tools/learning-list.cjs +0 -2
- package/bin/np-tools/learning-log.cjs +1 -7
- package/bin/np-tools/loop-audit-tool-use.cjs +1 -11
- package/bin/np-tools/loop-run-round.cjs +51 -148
- package/bin/np-tools/loop-state-read.cjs +1 -5
- package/bin/np-tools/loop-state-record.cjs +1 -27
- package/bin/np-tools/loop-stuck.cjs +1 -8
- package/bin/np-tools/messages-send.cjs +16 -2
- package/bin/np-tools/metrics.test.cjs +4 -4
- package/bin/np-tools/new-milestone.cjs +14 -3
- package/bin/np-tools/new-project.cjs +4 -2
- package/bin/np-tools/new-project.test.cjs +12 -0
- package/bin/np-tools/park.cjs +2 -1
- package/bin/np-tools/plan-lint.cjs +0 -19
- package/bin/np-tools/plan-milestone.cjs +8 -29
- package/bin/np-tools/propose-milestones.cjs +14 -3
- package/bin/np-tools/propose-milestones.test.cjs +27 -0
- package/bin/np-tools/research-phase.cjs +7 -37
- package/bin/np-tools/researcher-reconcile.cjs +3 -21
- package/bin/np-tools/reset-slice.cjs +10 -16
- package/bin/np-tools/resolve-model.cjs +21 -26
- package/bin/np-tools/resolve-model.test.cjs +15 -5
- package/bin/np-tools/resume-work.cjs +1 -5
- package/bin/np-tools/security.cjs +177 -0
- package/bin/np-tools/security.test.cjs +82 -0
- package/bin/np-tools/skip.cjs +2 -1
- package/bin/np-tools/spawn-headless.cjs +138 -19
- package/bin/np-tools/spawn-headless.test.cjs +310 -0
- package/bin/np-tools/state.cjs +0 -1
- package/bin/np-tools/undo-task.cjs +2 -1
- package/bin/np-tools/undo.cjs +5 -3
- package/bin/np-tools/unpark.cjs +2 -1
- package/bin/np-tools/verify-work.cjs +82 -25
- package/bin/np-tools/verify-work.test.cjs +211 -1
- package/bin/researcher-merge.cjs +2 -1
- package/bin/researcher-merge.test.cjs +14 -0
- package/lib/agents-registry.cjs +32 -0
- package/lib/agents.cjs +14 -6
- package/lib/agents.test.cjs +44 -0
- package/lib/archive.cjs +102 -36
- package/lib/archive.test.cjs +115 -5
- package/lib/checkpoint.cjs +43 -23
- package/lib/checkpoint.test.cjs +67 -6
- package/lib/commit-policy.cjs +3 -1
- package/lib/commit-policy.test.cjs +6 -0
- package/lib/config-defaults.cjs +28 -1
- package/lib/config-defaults.test.cjs +86 -0
- package/lib/config-schema.cjs +223 -0
- package/lib/config-schema.test.cjs +206 -0
- package/lib/config.cjs +168 -14
- package/lib/config.test.cjs +234 -0
- package/lib/core.cjs +226 -52
- package/lib/core.test.cjs +193 -10
- package/lib/dashboard.cjs +0 -12
- package/lib/frontmatter.cjs +5 -0
- package/lib/git.cjs +34 -27
- package/lib/git.test.cjs +11 -3
- package/lib/handoff.cjs +16 -14
- package/lib/handoff.test.cjs +24 -0
- package/lib/ids.cjs +6 -0
- package/lib/init-emit.cjs +33 -0
- package/lib/install/claude-hooks.cjs +145 -31
- package/lib/install/claude-hooks.test.cjs +160 -0
- package/lib/install/manifest.cjs +19 -0
- package/lib/install/manifest.test.cjs +107 -0
- package/lib/knowledge-adapter.cjs +3 -49
- package/lib/learnings.cjs +3 -108
- package/lib/logger.cjs +157 -0
- package/lib/logger.test.cjs +159 -0
- package/lib/memory-index-usearch.cjs +9 -12
- package/lib/memory-provider-local.cjs +8 -0
- package/lib/memory.cjs +86 -27
- package/lib/memory.test.cjs +135 -0
- package/lib/messaging.cjs +155 -83
- package/lib/metrics-aggregate.cjs +26 -27
- package/lib/metrics.cjs +7 -3
- package/lib/metrics.test.cjs +6 -5
- package/lib/migrations.cjs +89 -0
- package/lib/migrations.test.cjs +82 -0
- package/lib/milestone-meta.cjs +70 -0
- package/lib/nubosloop-audit.cjs +41 -141
- package/lib/nubosloop.cjs +45 -149
- package/lib/plan-lint.cjs +0 -67
- package/lib/researcher-swarm.cjs +1 -62
- package/lib/roadmap-render.cjs +107 -33
- package/lib/roadmap-schema.cjs +42 -0
- package/lib/roadmap.cjs +93 -20
- package/lib/roadmap.test.cjs +215 -0
- package/lib/run-context.cjs +54 -0
- package/lib/run-context.test.cjs +53 -0
- package/lib/runtime/index.cjs +5 -10
- package/lib/runtime/index.test.cjs +8 -1
- package/lib/safe-path.cjs +156 -0
- package/lib/safe-path.test.cjs +164 -0
- package/lib/security/ledger.cjs +203 -0
- package/lib/security/ledger.test.cjs +139 -0
- package/lib/security/patterns.cjs +119 -0
- package/lib/security/review.cjs +220 -0
- package/lib/security/review.test.cjs +143 -0
- package/lib/security/scan.cjs +180 -0
- package/lib/security/scan.test.cjs +137 -0
- package/lib/state.cjs +28 -10
- package/lib/state.test.cjs +72 -22
- package/lib/tasks.cjs +92 -14
- package/lib/tasks.test.cjs +65 -0
- package/lib/todo.cjs +7 -5
- package/lib/verify.cjs +44 -3
- package/lib/worktree.cjs +2 -2
- package/lib/yaml.cjs +44 -0
- package/lib/yaml.test.cjs +65 -0
- package/np-tools.cjs +26 -23
- package/package.json +5 -2
- package/templates/claude/payload/hooks/np-security-hook.cjs +50 -0
- package/workflows/execute-phase.md +11 -1
- package/workflows/research-phase.md +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to nubos-pilot are documented in this file. Format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning
|
|
5
|
+
follows [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [1.1.4] — 2026-05-25
|
|
8
|
+
|
|
9
|
+
Public release.
|
|
10
|
+
|
|
11
|
+
- Plan, execute, and verify code changes through a researcher + critic
|
|
12
|
+
agent loop.
|
|
13
|
+
- Wave-based milestone execution; one atomic git commit per task.
|
|
14
|
+
- Multi-runtime install for 14 host CLIs (Claude Code, Codex, Gemini,
|
|
15
|
+
OpenCode, Cursor, and more) via `npx nubos-pilot`.
|
|
16
|
+
- Local vector memory for cross-task learnings.
|
|
17
|
+
- Inter-agent messages, handoffs, and project archive with crash-safe
|
|
18
|
+
resume.
|
|
19
|
+
- Hardened filesystem operations: symlink-rejecting locks, restricted
|
|
20
|
+
permissions on audit logs, path containment for file-input flags,
|
|
21
|
+
frontmatter sanitisation, and a memory-model allow-list.
|
|
22
|
+
|
|
23
|
+
Full documentation at <https://pilot.nubos.cloud>.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nubos AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -146,6 +146,35 @@ npm test # all unit tests via node:test
|
|
|
146
146
|
node bin/check-workflows.cjs # workflow linter
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for setup, code conventions, ADR
|
|
150
|
+
map and commit format.
|
|
151
|
+
|
|
152
|
+
## Architecture Decisions
|
|
153
|
+
|
|
154
|
+
ADRs live in the VitePress at
|
|
155
|
+
[`pilot.nubos.cloud/v1/adr/`](https://pilot.nubos.cloud/v1/adr/). The
|
|
156
|
+
load-bearing ones for users and contributors:
|
|
157
|
+
|
|
158
|
+
| ADR | What it pins |
|
|
159
|
+
|---|---|
|
|
160
|
+
| 0004 | `workflow.commit_artifacts` controls whether `.nubos-pilot/` is committed |
|
|
161
|
+
| 0010 | Nubosloop — researcher → executor → critic-schwarm is mandatory in `/np:execute-phase` |
|
|
162
|
+
| 0012 | Completeness doctrine (12 rules in `templates/COMPLETENESS.md`) |
|
|
163
|
+
| 0013 | Learnings-store schema evolution |
|
|
164
|
+
| 0017 | Strict output-schema enforcement |
|
|
165
|
+
| 0019 | Plan-side trust layer (`lib/plan-lint.cjs`) |
|
|
166
|
+
|
|
167
|
+
## Security
|
|
168
|
+
|
|
169
|
+
See [`SECURITY.md`](./SECURITY.md) for the vulnerability disclosure policy
|
|
170
|
+
and threat model.
|
|
171
|
+
|
|
172
|
+
## Support
|
|
173
|
+
|
|
174
|
+
- Bugs / features: [GitHub issues](https://github.com/Nubos-AI/nubos-pilot/issues)
|
|
175
|
+
- Security: `security@nubos.ai` (see [`SECURITY.md`](./SECURITY.md))
|
|
176
|
+
- Docs: <https://pilot.nubos.cloud>
|
|
177
|
+
|
|
149
178
|
## License
|
|
150
179
|
|
|
151
|
-
MIT
|
|
180
|
+
MIT — see [`LICENSE`](./LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security issue in nubos-pilot, **do not open a public issue**.
|
|
6
|
+
Email **security@nubos.ai** with:
|
|
7
|
+
|
|
8
|
+
- A description of the issue and its impact.
|
|
9
|
+
- Steps to reproduce (PoC if possible).
|
|
10
|
+
- The affected version (`npx nubos-pilot --version` or check `package.json`).
|
|
11
|
+
- Your preferred contact channel for follow-up.
|
|
12
|
+
|
|
13
|
+
We will acknowledge receipt within **3 business days** and provide a
|
|
14
|
+
resolution plan within **14 business days**. Fixes are released as patch
|
|
15
|
+
versions and announced in `CHANGELOG.md`.
|
|
16
|
+
|
|
17
|
+
## Supported Versions
|
|
18
|
+
|
|
19
|
+
| Version | Supported |
|
|
20
|
+
|---------|-----------|
|
|
21
|
+
| 0.2.x | ✅ active |
|
|
22
|
+
| < 0.2 | ❌ end of life |
|
|
23
|
+
|
|
24
|
+
Only the latest minor on the current major receives security patches until
|
|
25
|
+
1.0 is reached.
|
|
26
|
+
|
|
27
|
+
## Threat Model
|
|
28
|
+
|
|
29
|
+
nubos-pilot is a **local CLI** distributed via npm to developer workstations
|
|
30
|
+
and CI. It is **not** a hosted service. The threat surface and assumptions:
|
|
31
|
+
|
|
32
|
+
| What nubos-pilot reads | What it writes | What it executes |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `.nubos-pilot/`, project source for context | `.nubos-pilot/` state, `~/.codex/`, `~/.claude/` config (install only) | `git`, `claude`/`codex` headless via `child_process.spawn` |
|
|
35
|
+
|
|
36
|
+
**Trust boundaries:**
|
|
37
|
+
|
|
38
|
+
- **Project source code** — untrusted in the sense that agent-authored
|
|
39
|
+
files (`PLAN.md`, `RESEARCH.md` etc.) may contain hostile YAML. nubos-pilot
|
|
40
|
+
rejects prototype-pollution keys, refuses symlink-escape via `safe-path`,
|
|
41
|
+
caps message bodies, and whitelists ML model identifiers.
|
|
42
|
+
- **`.nubos-pilot/messages/`** — multi-agent inbox; entries are written
|
|
43
|
+
atomically with `O_CREAT|O_EXCL|O_NOFOLLOW` (POSIX) so a pre-planted
|
|
44
|
+
symlink cannot redirect writes.
|
|
45
|
+
- **Subprocess spawn** — `claude`/`codex` are invoked via `spawnSync` (no
|
|
46
|
+
shell). The binary path is overridable via `NUBOS_PILOT_CLAUDE_BIN` /
|
|
47
|
+
`NUBOS_PILOT_CODEX_BIN`; treat operators who can set those env vars as
|
|
48
|
+
trusted.
|
|
49
|
+
- **`workflow.commit_artifacts`** flag controls whether `.nubos-pilot/`
|
|
50
|
+
artifacts are committed to git. Default is `true`; downstream projects
|
|
51
|
+
that consider artifacts sensitive should set it to `false`.
|
|
52
|
+
|
|
53
|
+
## What is Out of Scope
|
|
54
|
+
|
|
55
|
+
- Vulnerabilities in `@huggingface/transformers`, `usearch`, or the
|
|
56
|
+
`yaml` package — report those upstream.
|
|
57
|
+
- Operator-controlled config (`config.json`) that the operator themselves
|
|
58
|
+
wrote — config is trusted input from the project owner.
|
|
59
|
+
- DoS from running nubos-pilot in obviously bad conditions
|
|
60
|
+
(no disk space, no Node 22+, broken `git`).
|
package/agents/np-executor.md
CHANGED
|
@@ -49,6 +49,25 @@ The orchestrator provides these in your prompt context. Read every path it hands
|
|
|
49
49
|
| Task summary (write on completion) | You fill this after the commit lands — describes changes, verification, follow-ups. | `.nubos-pilot/milestones/M<NNN>/slices/S<NNN>/tasks/T<NNNN>/T<NNNN>-SUMMARY.md` |
|
|
50
50
|
| Checkpoint file (managed) | Write-through state transitions via `np-tools.cjs checkpoint transition`. Do NOT read/write directly. | `.nubos-pilot/checkpoints/<task-full-id>.json` |
|
|
51
51
|
|
|
52
|
+
## Write against the success_criteria
|
|
53
|
+
|
|
54
|
+
When the orchestrator includes a `<success_criteria>` block in your prompt, those criteria are the
|
|
55
|
+
milestone's **acceptance target** — what "done right" means. Use them as your north star while you
|
|
56
|
+
implement, not just the `verify` command. `verify` proves the code runs; the criteria prove it does
|
|
57
|
+
the *right* thing. Aim for both green.
|
|
58
|
+
|
|
59
|
+
- **Intent, not a build spec (ADR-0019).** Criteria say *what* must be true, never *how* to build it
|
|
60
|
+
(no schema/filename/style is implied). Don't treat a criterion as a licence to add structure the
|
|
61
|
+
task plan didn't ask for.
|
|
62
|
+
- **Stay in scope.** A criterion is **never** a reason to edit a path outside `files_modified`. If
|
|
63
|
+
satisfying it would require touching another file, that is a planner-scope bug — emit the
|
|
64
|
+
`## SCOPE EXPANSION REQUEST` block (step 4a) and hand back; do not expand scope.
|
|
65
|
+
- **Self-check before commit.** Before `commit-task`, re-read your diff against each criterion your
|
|
66
|
+
task contributes to (cross-reference the slice `S<NNN>-UAT.md`). If your in-scope change leaves a
|
|
67
|
+
criterion it should satisfy unmet, fix it within `files_modified` before committing — don't ship a
|
|
68
|
+
known gap for the critic to bounce back.
|
|
69
|
+
- Criteria outside your task's scope are context, not your responsibility — do not chase them.
|
|
70
|
+
|
|
52
71
|
## Codebase Docs Protocol (runtime-agnostic)
|
|
53
72
|
|
|
54
73
|
nubos-pilot maintains a skill-style code documentation layer at
|
|
@@ -131,6 +150,7 @@ into the `task(…)` commit. If `workflow.commit_docs=true`, the
|
|
|
131
150
|
<scope_guardrail>
|
|
132
151
|
**Do:**
|
|
133
152
|
- Edit only files enumerated in `files_modified`.
|
|
153
|
+
- Treat any `<success_criteria>` in your prompt as the acceptance target; self-check your diff against it before commit (see "Write against the success_criteria").
|
|
134
154
|
- Commit via `node np-tools.cjs commit-task <task-id>`.
|
|
135
155
|
- Write checkpoint state transitions via the wrapper.
|
|
136
156
|
- Stay within the task's declared scope even if you spot tangential issues — log them, do not fix them.
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: np-security-reviewer
|
|
3
|
-
description: Read-only
|
|
3
|
+
description: Read-only security auditor with two input modes. Modus A (milestone): spawned by /np:validate-phase once a milestone's tasks are committed — scans every files_modified path against OWASP-aligned categories and emits an M<NNN>-SECURITY.md draft with Pass/Risk/Defer per finding. Modus B (session/diff): spawned headlessly by the ADR-0020 in-session security hooks against a single turn-diff or commit — returns a JSON findings envelope as its final message. Detection-only in both modes — never edits source.
|
|
4
4
|
tier: sonnet
|
|
5
5
|
tools: Read, Bash, Grep, Glob
|
|
6
6
|
color: red
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
<role>
|
|
10
|
-
You are the nubos-pilot security reviewer. Post-execution twin of `np-verifier` for the security surface.
|
|
10
|
+
You are the nubos-pilot security reviewer. Post-execution twin of `np-verifier` for the security surface. You run in one of two modes, decided by the prompt.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**Modus A — milestone audit (default).** Spawned once a milestone's task commits are in place. You emit a `M<NNN>-SECURITY.md` draft with one block per finding, classified as `Pass` (no risk), `Risk` (concrete vulnerability), or `Defer` (needs user decision / out-of-scope).
|
|
13
|
+
|
|
14
|
+
**Modus B — session/diff (ADR-0020).** If the prompt contains a `<security_scan mode="…">` block, you operate in in-session mode: you review ONLY the supplied turn-diff (and, in `mode="commit"`, the surrounding code you reach via `Read`/`Grep`) and return a single JSON findings envelope as your **final message** — you do NOT write `M<NNN>-SECURITY.md`, do NOT use a milestone number, and do NOT read milestone files. See "## Session/Diff Mode (Modus B)" below for the exact contract.
|
|
15
|
+
|
|
16
|
+
You DO NOT propose patches. You DO NOT edit source. You report — in both modes.
|
|
13
17
|
|
|
14
18
|
**CRITICAL: Mandatory Initial Read**
|
|
15
19
|
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
|
@@ -104,6 +108,48 @@ Milestone Status resolution:
|
|
|
104
108
|
- Else any `Defer` → `deferred`.
|
|
105
109
|
- Else → `clean`.
|
|
106
110
|
|
|
111
|
+
## Session/Diff Mode (Modus B) — ADR-0020
|
|
112
|
+
|
|
113
|
+
Triggered when the prompt contains a `<security_scan mode="stop|commit">` block. This is the in-session
|
|
114
|
+
review spawned by the security hooks. It is independent by construction: you receive only the diff and a
|
|
115
|
+
fresh context — you never graded the code you are reviewing.
|
|
116
|
+
|
|
117
|
+
**Inputs (all inside the `<security_scan>` block):**
|
|
118
|
+
- The list of changed files and the diff under review.
|
|
119
|
+
- `mode="stop"` — review only what the turn changed; start from the diff, do not hunt outside it.
|
|
120
|
+
- `mode="commit"` — a deeper pass: use `Read`/`Grep`/`Glob` to inspect surrounding code (callers,
|
|
121
|
+
sanitizers, related files) before deciding a finding is real, to keep false positives low.
|
|
122
|
+
- An optional project guidance block. It is **additive** — it adds checks on top of the built-in OWASP
|
|
123
|
+
categories and never disables them. `RULES.md`/`CONTEXT.md` (if referenced) still authorize/neutralize
|
|
124
|
+
a finding the same way as Modus A.
|
|
125
|
+
|
|
126
|
+
**Behaviour:**
|
|
127
|
+
- Apply the same OWASP-aligned categories as Modus A.
|
|
128
|
+
- Report ONLY concrete `Risk` findings. Omit `Pass`/no-risk entries entirely.
|
|
129
|
+
- Do NOT write any file. Do NOT edit source. Do NOT spawn agents. Do NOT use a milestone number.
|
|
130
|
+
|
|
131
|
+
**Output contract — your FINAL message MUST be exactly one JSON object, no prose, no code fence:**
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"status": "clean | risks-found",
|
|
136
|
+
"findings": [
|
|
137
|
+
{
|
|
138
|
+
"category": "Injection | Auth & Session | Access Control | Crypto | SSRF / Open Redirect | Deserialization | File / Path | Secrets | Logging | Dependencies",
|
|
139
|
+
"severity": "high | medium | low",
|
|
140
|
+
"file": "relative/path.ext",
|
|
141
|
+
"line": 42,
|
|
142
|
+
"title": "short finding title",
|
|
143
|
+
"evidence": "the matched line / why it is exploitable",
|
|
144
|
+
"mitigation_hint": "the real fix (a pointer, not a patch)"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
If you find nothing, return `{"status":"clean","findings":[]}`. The orchestrator surfaces and fixes these
|
|
151
|
+
findings as a follow-up in the same conversation — it never blocks the write or commit.
|
|
152
|
+
|
|
107
153
|
## Handoff Protocol
|
|
108
154
|
|
|
109
155
|
Before reviewing, check handoffs addressed to `np-security-reviewer`:
|
package/bin/install.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('node:fs');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const os = require('node:os');
|
|
7
7
|
|
|
8
|
-
const { atomicWriteFileSync, withFileLock, NubosPilotError } = require('../lib/core.cjs');
|
|
8
|
+
const { atomicWriteFileSync, withFileLock, installSignalCleanup, NubosPilotError } = require('../lib/core.cjs');
|
|
9
9
|
const { askUser: defaultAskUser } = require('../lib/askuser.cjs');
|
|
10
10
|
const manifestMod = require('../lib/install/manifest.cjs');
|
|
11
11
|
const stagingMod = require('../lib/install/staging.cjs');
|
|
@@ -159,6 +159,10 @@ function _renderShim(target, mode) {
|
|
|
159
159
|
return '#!/usr/bin/env node\n'
|
|
160
160
|
+ "'use strict';\n"
|
|
161
161
|
+ 'const fs = require(\'node:fs\');\n'
|
|
162
|
+
+ 'if (Number(process.versions.node.split(\'.\')[0]) < 22) {\n'
|
|
163
|
+
+ ' process.stderr.write("nubos-pilot: requires Node >= 22 (running " + process.versions.node + ")\\n");\n'
|
|
164
|
+
+ ' process.exit(1);\n'
|
|
165
|
+
+ '}\n'
|
|
162
166
|
+ 'const TARGET = ' + JSON.stringify(target) + ';\n'
|
|
163
167
|
+ 'if (!fs.existsSync(TARGET)) {\n'
|
|
164
168
|
+ ' process.stderr.write("nubos-pilot: tool binary fehlt unter " + TARGET + "\\nFix: npx nubos-pilot@latest update\\n");\n'
|
|
@@ -170,12 +174,18 @@ function _renderShim(target, mode) {
|
|
|
170
174
|
+ "'use strict';\n"
|
|
171
175
|
+ 'const fs = require(\'node:fs\');\n'
|
|
172
176
|
+ 'const { spawn } = require(\'node:child_process\');\n'
|
|
177
|
+
+ 'if (Number(process.versions.node.split(\'.\')[0]) < 22) {\n'
|
|
178
|
+
+ ' process.stderr.write("nubos-pilot: requires Node >= 22 (running " + process.versions.node + ")\\n");\n'
|
|
179
|
+
+ ' process.exit(1);\n'
|
|
180
|
+
+ '}\n'
|
|
173
181
|
+ 'const TARGET = ' + JSON.stringify(target) + ';\n'
|
|
174
182
|
+ 'if (!fs.existsSync(TARGET)) {\n'
|
|
175
183
|
+ ' process.stderr.write("nubos-pilot: tool binary fehlt unter " + TARGET + "\\nFix: npx nubos-pilot@latest update\\n");\n'
|
|
176
184
|
+ ' process.exit(1);\n'
|
|
177
185
|
+ '}\n'
|
|
178
186
|
+ 'const child = spawn(process.execPath, [TARGET, ...process.argv.slice(2)], { stdio: \'inherit\' });\n'
|
|
187
|
+
+ 'child.on(\'error\', (err) => { process.stderr.write("nubos-pilot shim: " + (err && err.message ? err.message : String(err)) + "\\n"); process.exit(1); });\n'
|
|
188
|
+
+ 'for (const s of [\'SIGINT\', \'SIGTERM\', \'SIGHUP\']) { process.on(s, () => { try { child.kill(s); } catch {} }); }\n'
|
|
179
189
|
+ 'child.on(\'exit\', (code, sig) => { if (sig) process.kill(process.pid, sig); else process.exit(code == null ? 1 : code); });\n';
|
|
180
190
|
}
|
|
181
191
|
|
|
@@ -197,24 +207,38 @@ function _stateDirFor(projectRoot) {
|
|
|
197
207
|
return path.join(projectRoot, STATE_SUBPATH);
|
|
198
208
|
}
|
|
199
209
|
|
|
200
|
-
function
|
|
210
|
+
function _readInstallConfig(projectRoot) {
|
|
201
211
|
const cfgPath = path.join(_stateDirFor(projectRoot), 'config.json');
|
|
202
212
|
if (!fs.existsSync(cfgPath)) return null;
|
|
213
|
+
const { _CONFIG_PARSE_CODES, readConfig } = require('../lib/config.cjs');
|
|
214
|
+
const { NubosPilotError } = require('../lib/core.cjs');
|
|
203
215
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
216
|
+
return readConfig(projectRoot);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (err && err.code === 'not-in-project') return null;
|
|
219
|
+
if (err && _CONFIG_PARSE_CODES.has(err.code)) {
|
|
220
|
+
throw new NubosPilotError(
|
|
221
|
+
'install-config-unusable',
|
|
222
|
+
'install refused — .nubos-pilot/config.json is unusable (' + err.code
|
|
223
|
+
+ '). Repair or delete the file and re-run.',
|
|
224
|
+
{ cause: err.code },
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _readExistingScope(projectRoot) {
|
|
232
|
+
const cfg = _readInstallConfig(projectRoot);
|
|
233
|
+
return cfg && cfg.scope ? cfg.scope : null;
|
|
207
234
|
}
|
|
208
235
|
|
|
209
236
|
function _readExistingRuntimes(projectRoot) {
|
|
210
|
-
const
|
|
211
|
-
if (!
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (cfg.runtime) return [cfg.runtime];
|
|
216
|
-
return null;
|
|
217
|
-
} catch { return null; }
|
|
237
|
+
const cfg = _readInstallConfig(projectRoot);
|
|
238
|
+
if (!cfg) return null;
|
|
239
|
+
if (Array.isArray(cfg.runtimes) && cfg.runtimes.length) return cfg.runtimes.slice();
|
|
240
|
+
if (cfg.runtime) return [cfg.runtime];
|
|
241
|
+
return null;
|
|
218
242
|
}
|
|
219
243
|
|
|
220
244
|
function detectMode(projectRoot, scope) {
|
|
@@ -277,10 +301,19 @@ async function _runInitQuestions(detectedRuntime, askUser, flags) {
|
|
|
277
301
|
const model_profile = (await askUser({ type: 'select', question: 'Model-Profile?',
|
|
278
302
|
options: ['frontier', 'quality', 'balanced', 'budget', 'inherit'], default: 'frontier' })).value;
|
|
279
303
|
const response_language = (await askUser({ type: 'input', question: 'Response language (ISO-639 code)?', default: 'en' })).value;
|
|
304
|
+
// Wizard / --yes default is intentionally `false` (safer-by-default per
|
|
305
|
+
// FIX-B2) even though the implicit code default lives at `true` in
|
|
306
|
+
// DEFAULT_WORKFLOW (ADR-0004). The two are NOT in drift: explicit answer
|
|
307
|
+
// overrides default; absent key falls back to ADR-0004 true. This is
|
|
308
|
+
// covered by tests/install/install-flags.test.cjs:85.
|
|
309
|
+
const commit_artifacts = (await askUser({ type: 'confirm',
|
|
310
|
+
question: 'Auto-commit nubos-pilot planning artefacts (.nubos-pilot/ — milestones, roadmap, learnings) into your git repo?',
|
|
311
|
+
default: false })).value;
|
|
280
312
|
return configDefaults.buildInstallConfig({
|
|
281
313
|
runtime, runtimes, scope,
|
|
282
314
|
model_profile,
|
|
283
315
|
response_language,
|
|
316
|
+
commit_artifacts,
|
|
284
317
|
});
|
|
285
318
|
}
|
|
286
319
|
|
|
@@ -476,8 +509,19 @@ async function _runInstallLocked(ctx) {
|
|
|
476
509
|
}
|
|
477
510
|
|
|
478
511
|
stagingMod.finalizeSwap(payloadBase);
|
|
512
|
+
const resolvedPayloadDir = path.resolve(payloadDir);
|
|
479
513
|
for (const rel of diff.stale) {
|
|
480
|
-
|
|
514
|
+
manifestMod.assertSafeManifestKey(rel, 'install-stale-cleanup');
|
|
515
|
+
const abs = path.join(payloadDir, rel);
|
|
516
|
+
const resolvedAbs = path.resolve(abs);
|
|
517
|
+
if (!(resolvedAbs === resolvedPayloadDir || resolvedAbs.startsWith(resolvedPayloadDir + path.sep))) {
|
|
518
|
+
throw new NubosPilotError(
|
|
519
|
+
'manifest-unlink-outside-base',
|
|
520
|
+
'Refusing unlink that escapes payloadDir',
|
|
521
|
+
{ rel, base: path.basename(payloadDir) },
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
try { fs.unlinkSync(abs); } catch {}
|
|
481
525
|
}
|
|
482
526
|
|
|
483
527
|
if (opencodeManifest) {
|
|
@@ -503,9 +547,20 @@ async function _runInstallLocked(ctx) {
|
|
|
503
547
|
const opencodeBase = resolvedScope === 'global' ? os.homedir() : projectRoot;
|
|
504
548
|
for (const rel of diff.stale) {
|
|
505
549
|
if (rel.startsWith(opencodeManifestPrefix)) {
|
|
550
|
+
manifestMod.assertSafeManifestKey(rel, 'install-opencode-stale');
|
|
506
551
|
const relFs = rel.startsWith('~/')
|
|
507
552
|
? path.join(os.homedir(), rel.slice(2))
|
|
508
553
|
: path.join(opencodeBase, rel);
|
|
554
|
+
const expectedBase = rel.startsWith('~/') ? os.homedir() : opencodeBase;
|
|
555
|
+
const resolvedRelFs = path.resolve(relFs);
|
|
556
|
+
const resolvedExpected = path.resolve(expectedBase);
|
|
557
|
+
if (!(resolvedRelFs === resolvedExpected || resolvedRelFs.startsWith(resolvedExpected + path.sep))) {
|
|
558
|
+
throw new NubosPilotError(
|
|
559
|
+
'manifest-unlink-outside-base',
|
|
560
|
+
'Refusing opencode unlink that escapes its base',
|
|
561
|
+
{ rel, base: path.basename(expectedBase) },
|
|
562
|
+
);
|
|
563
|
+
}
|
|
509
564
|
try { fs.unlinkSync(relFs); } catch {}
|
|
510
565
|
}
|
|
511
566
|
}
|
|
@@ -555,10 +610,15 @@ async function _runInstallLocked(ctx) {
|
|
|
555
610
|
try {
|
|
556
611
|
const claudeHooks = require('../lib/install/claude-hooks.cjs');
|
|
557
612
|
const res = claudeHooks.installClaudeHooks({
|
|
558
|
-
projectRoot, scope: resolvedScope, which: '
|
|
613
|
+
projectRoot, scope: resolvedScope, which: 'all', force: false,
|
|
559
614
|
});
|
|
615
|
+
const secAction = res.results.security
|
|
616
|
+
? Object.values(res.results.security).every((r) => r.action === 'installed') ? 'installed'
|
|
617
|
+
: Object.values(res.results.security).every((r) => r.action === 'updated') ? 'updated' : 'mixed'
|
|
618
|
+
: 'skipped';
|
|
560
619
|
console.error(dim + ' [claude-hooks] statusline: ' + res.results.statusline.action
|
|
561
|
-
+ ', ctx-monitor: ' + res.results.ctxMonitor.action
|
|
620
|
+
+ ', ctx-monitor: ' + res.results.ctxMonitor.action
|
|
621
|
+
+ ', security: ' + secAction + reset);
|
|
562
622
|
if (res.results.statusline.action === 'skipped-existing') {
|
|
563
623
|
console.error(yellow + ' [claude-hooks] foreign statusLine preserved — re-run `install-hooks --force` to overwrite' + reset);
|
|
564
624
|
}
|
|
@@ -594,14 +654,11 @@ function _runUninstallLocked(projectRoot) {
|
|
|
594
654
|
return { uninstalled: false };
|
|
595
655
|
}
|
|
596
656
|
|
|
657
|
+
// Reuse the SAME validator as readManifest so a legitimate key like
|
|
658
|
+
// `..bar` (no traversal segment) isn't false-rejected here while passing
|
|
659
|
+
// validation upstream. Single source of truth lives in manifest.cjs.
|
|
597
660
|
for (const rel of Object.keys(manifest.files)) {
|
|
598
|
-
|
|
599
|
-
throw new NubosPilotError(
|
|
600
|
-
'manifest-path-traversal',
|
|
601
|
-
'Manifest contains suspicious path',
|
|
602
|
-
{ rel },
|
|
603
|
-
);
|
|
604
|
-
}
|
|
661
|
+
manifestMod.assertSafeManifestKey(rel, 'uninstall');
|
|
605
662
|
}
|
|
606
663
|
|
|
607
664
|
const payloadBase = scope === 'global' ? os.homedir() : projectRoot;
|
|
@@ -612,6 +669,20 @@ function _runUninstallLocked(projectRoot) {
|
|
|
612
669
|
const abs = rel.startsWith('~/')
|
|
613
670
|
? path.join(os.homedir(), rel.slice(2))
|
|
614
671
|
: isAsset ? path.join(payloadBase, rel) : path.join(payloadDir, rel);
|
|
672
|
+
// Defense-in-depth: even with the validator above, ensure the resolved
|
|
673
|
+
// path lives inside its expected base. A symlink or future-validator
|
|
674
|
+
// regression cannot escape this prefix check.
|
|
675
|
+
const expectedBase = rel.startsWith('~/') ? os.homedir()
|
|
676
|
+
: isAsset ? payloadBase : payloadDir;
|
|
677
|
+
const resolvedAbs = path.resolve(abs);
|
|
678
|
+
const resolvedBase = path.resolve(expectedBase);
|
|
679
|
+
if (!(resolvedAbs === resolvedBase || resolvedAbs.startsWith(resolvedBase + path.sep))) {
|
|
680
|
+
throw new NubosPilotError(
|
|
681
|
+
'manifest-unlink-outside-base',
|
|
682
|
+
'Refusing unlink that escapes its payload base',
|
|
683
|
+
{ rel, base: path.basename(expectedBase) },
|
|
684
|
+
);
|
|
685
|
+
}
|
|
615
686
|
try {
|
|
616
687
|
fs.unlinkSync(abs);
|
|
617
688
|
removed++;
|
|
@@ -639,12 +710,11 @@ function _runUninstallLocked(projectRoot) {
|
|
|
639
710
|
|
|
640
711
|
try { fs.rmdirSync(payloadDir); } catch {}
|
|
641
712
|
|
|
642
|
-
const cfgPath = path.join(_stateDirFor(projectRoot), 'config.json');
|
|
643
713
|
let installedRuntimes = [];
|
|
644
|
-
|
|
645
|
-
|
|
714
|
+
const cfg = _readInstallConfig(projectRoot);
|
|
715
|
+
if (cfg) {
|
|
646
716
|
installedRuntimes = cfg.runtimes || (cfg.runtime ? [cfg.runtime] : []);
|
|
647
|
-
}
|
|
717
|
+
}
|
|
648
718
|
|
|
649
719
|
const legacyFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
|
|
650
720
|
const extraFiles = [];
|
|
@@ -793,21 +863,21 @@ async function runUninstallHooks(opts) {
|
|
|
793
863
|
}
|
|
794
864
|
|
|
795
865
|
if (require.main === module) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
process.stderr.write(
|
|
799
|
-
JSON.stringify({
|
|
800
|
-
error: {
|
|
801
|
-
code: err.code,
|
|
802
|
-
message: err.message,
|
|
803
|
-
details: err.details || null,
|
|
804
|
-
},
|
|
805
|
-
}) + '\n',
|
|
806
|
-
);
|
|
807
|
-
} else {
|
|
808
|
-
process.stderr.write(((err && err.stack) || String(err)) + '\n');
|
|
809
|
-
}
|
|
866
|
+
if (Number(process.versions.node.split('.')[0]) < 22) {
|
|
867
|
+
process.stderr.write('nubos-pilot: requires Node >= 22 (running ' + process.versions.node + ')\n');
|
|
810
868
|
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
installSignalCleanup();
|
|
871
|
+
main().catch((err) => {
|
|
872
|
+
const payload = (err && err.code)
|
|
873
|
+
? JSON.stringify({ error: { code: err.code, message: err.message, details: err.details || null } }) + '\n'
|
|
874
|
+
: ((err && err.stack) || String(err)) + '\n';
|
|
875
|
+
// Drain stderr before exit. process.exit() can otherwise tear down the
|
|
876
|
+
// pipe mid-flush on busy CI, truncating the envelope. Set exitCode and
|
|
877
|
+
// let Node drain naturally; force-exit only as a last-resort fallback.
|
|
878
|
+
try { process.stderr.write(payload); } catch {}
|
|
879
|
+
process.exitCode = 1;
|
|
880
|
+
setTimeout(() => process.exit(1), 1000).unref();
|
|
811
881
|
});
|
|
812
882
|
}
|
|
813
883
|
|
package/bin/np-tools/_args.cjs
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
4
|
|
|
5
|
-
function getFlag(rest, name) {
|
|
5
|
+
function getFlag(rest, name, opts) {
|
|
6
6
|
const idx = rest.indexOf(name);
|
|
7
|
-
|
|
7
|
+
if (idx === -1) return undefined;
|
|
8
|
+
const next = rest[idx + 1];
|
|
9
|
+
const allowDash = opts && opts.allowDashValues === true;
|
|
10
|
+
if (!allowDash && typeof next === 'string' && next.startsWith('--')) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return next;
|
|
8
14
|
}
|
|
9
15
|
|
|
10
16
|
function getJsonFlag(rest, name, missingCode, hint) {
|
|
@@ -96,6 +96,7 @@ const COMMANDS = [
|
|
|
96
96
|
{ name: 'loop-audit-tool-use', category: 'Execution', description: 'Record/read the tool-use audit per spawn (Completeness Rule 9 mechanical check)', description_de: 'Tool-use Audit pro Spawn schreiben/lesen (Completeness Rule 9 mechanische Prüfung)' },
|
|
97
97
|
{ name: 'loop-stuck', category: 'Execution', description: 'Mark a task as stuck (writes loop-state + flips checkpoint status to stuck)', description_de: 'Markiert Task als stuck (schreibt Loop-State + setzt Checkpoint-Status auf stuck)' },
|
|
98
98
|
{ name: 'spawn-headless', category: 'Execution', description: 'Spawn an agent as a headless `claude -p` subprocess (ADR-0010 §L6); writes stdout to --output-path and returns exit code', description_de: 'Spawnt einen Agent als headless `claude -p` Subprozess (ADR-0010 §L6); schreibt stdout nach --output-path und liefert Exit-Code' },
|
|
99
|
+
{ name: 'security', category: 'Review', description: 'In-session security review hook backend (ADR-0020). Verbs: session-start | baseline | scan | review | commit | run-review. Reads the Claude Code hook payload via --stdin; non-blocking, report-once, independent reviewer spawn.', description_de: 'Backend für die In-Session-Security-Review-Hooks (ADR-0020). Verben: session-start | baseline | scan | review | commit | run-review. Liest die Claude-Code-Hook-Payload via --stdin; non-blocking, report-once, unabhängiger Reviewer-Spawn.' },
|
|
99
100
|
{ name: 'loop-metrics', category: 'Utility', description: 'Aggregate Nubosloop telemetry across all checkpoints (commits, stuck, route distribution)', description_de: 'Aggregiert Nubosloop-Telemetrie über alle Checkpoints (Commits, Stuck, Routing)' },
|
|
100
101
|
{ name: 'learning-log', category: 'Execution', description: 'Persist a learning to the local store (or MCP adapter when configured)', description_de: 'Persistiert ein Learning im lokalen Store (oder MCP-Adapter falls konfiguriert)' },
|
|
101
102
|
{ name: 'learning-match', category: 'Utility', description: 'Query the learnings store for cached patterns matching a free-text query', description_de: 'Fragt den Learnings-Store nach Cached-Patterns ab' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
-
const {
|
|
4
|
+
const { tryReadConfigPath } = require('../../lib/config.cjs');
|
|
5
5
|
const { createMemory } = require('../../lib/memory.cjs');
|
|
6
6
|
|
|
7
7
|
function resolveMemory(opts) {
|
|
@@ -17,7 +17,7 @@ function resolveMemory(opts) {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const enabled =
|
|
20
|
+
const enabled = tryReadConfigPath(cwd, 'memory.enabled', false);
|
|
21
21
|
if (!enabled) {
|
|
22
22
|
throw new NubosPilotError(
|
|
23
23
|
'memory-disabled',
|
|
@@ -26,8 +26,8 @@ function resolveMemory(opts) {
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const model =
|
|
30
|
-
const alpha =
|
|
29
|
+
const model = tryReadConfigPath(cwd, 'memory.model', 'Xenova/bge-small-en-v1.5');
|
|
30
|
+
const alpha = tryReadConfigPath(cwd, 'memory.alpha', 0.6);
|
|
31
31
|
|
|
32
32
|
const { createLocalProvider } = require('../../lib/memory-provider-local.cjs');
|
|
33
33
|
const { createUsearchIndex } = require('../../lib/memory-index-usearch.cjs');
|
|
@@ -1,36 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const crypto = require('node:crypto');
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
NubosPilotError,
|
|
10
|
-
projectStateDir,
|
|
11
|
-
} = require('../../lib/core.cjs');
|
|
3
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { emitInitPayload } = require('../../lib/init-emit.cjs');
|
|
12
5
|
const archive = require('../../lib/archive.cjs');
|
|
13
6
|
const textMode = require('../../lib/text-mode.cjs');
|
|
14
7
|
|
|
15
|
-
const INLINE_THRESHOLD_BYTES = 16 * 1024;
|
|
16
|
-
|
|
17
|
-
function _emit(payload, stdout, cwd) {
|
|
18
|
-
const json = JSON.stringify(payload, null, 2);
|
|
19
|
-
if (Buffer.byteLength(json, 'utf-8') <= INLINE_THRESHOLD_BYTES) {
|
|
20
|
-
stdout.write(json);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
let tmpDir;
|
|
24
|
-
try {
|
|
25
|
-
tmpDir = path.join(projectStateDir(cwd), '.tmp');
|
|
26
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
27
|
-
} catch { tmpDir = os.tmpdir(); }
|
|
28
|
-
const suffix = process.pid + '-' + crypto.randomBytes(4).toString('hex');
|
|
29
|
-
const tmpPath = path.join(tmpDir, 'init-close-project-' + suffix + '.json');
|
|
30
|
-
fs.writeFileSync(tmpPath, json, 'utf-8');
|
|
31
|
-
stdout.write('@file:' + tmpPath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
8
|
function _initPayload(cwd) {
|
|
35
9
|
const completion = archive.computeCompletionStatus(cwd);
|
|
36
10
|
const tmDetail = textMode.resolveTextModeDetail(cwd);
|
|
@@ -68,7 +42,7 @@ function run(args, ctx) {
|
|
|
68
42
|
case 'init':
|
|
69
43
|
case undefined: {
|
|
70
44
|
const payload = _initPayload(cwd);
|
|
71
|
-
|
|
45
|
+
emitInitPayload(payload, stdout, cwd, 'close-project');
|
|
72
46
|
return payload;
|
|
73
47
|
}
|
|
74
48
|
case 'check': {
|