nubos-pilot 0.8.0 → 0.8.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/README.md +14 -14
- package/agents/np-architect.md +132 -0
- package/agents/np-build-fixer.md +98 -0
- package/agents/np-planner.md +1 -1
- package/agents/np-security-reviewer.md +128 -0
- package/bin/np-tools/_commands.cjs +7 -2
- package/bin/np-tools/context-stats.cjs +117 -0
- package/bin/np-tools/knowledge-index.cjs +23 -0
- package/bin/np-tools/knowledge-search.cjs +36 -0
- package/bin/np-tools/knowledge-stats.cjs +19 -0
- package/bin/np-tools/new-project.cjs +9 -0
- package/bin/np-tools/pause-work.cjs +31 -0
- package/bin/np-tools/resume-work.cjs +12 -0
- package/bin/np-tools/session-snapshot-read.cjs +14 -0
- package/bin/np-tools/session-snapshot-write.cjs +44 -0
- package/lib/agents.test.cjs +3 -0
- package/lib/knowledge.cjs +256 -0
- package/lib/knowledge.test.cjs +83 -0
- package/lib/session-snapshot.cjs +93 -0
- package/lib/session-snapshot.test.cjs +73 -0
- package/mcp-configs/README.md +41 -0
- package/mcp-configs/claude-code.example.json +27 -0
- package/mcp-configs/codex.example.toml +17 -0
- package/mcp-configs/nubos-knowledge.notes.md +42 -0
- package/np-tools.cjs +6 -0
- package/package.json +4 -1
- package/templates/RULES.md +95 -0
- package/workflows/add-todo.md +2 -5
- package/workflows/architect-phase.md +107 -0
- package/workflows/context-stats.md +60 -0
- package/workflows/knowledge.md +76 -0
- package/workflows/note.md +2 -2
- package/workflows/research-phase.md +3 -2
- package/workflows/session-report.md +0 -2
- package/workflows/stats.md +2 -1
- package/workflows/thread.md +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nubos-pilot
|
|
2
2
|
|
|
3
|
-
AI-driven planning and execution tool for code projects. Installs into Claude Code, Codex, Gemini, OpenCode, Cursor and
|
|
3
|
+
AI-driven planning and execution tool for code projects. Installs into 14 host CLIs (Claude Code, Codex, Gemini, OpenCode, Cursor and ten more) as a set of Markdown workflows + subagents.
|
|
4
4
|
|
|
5
5
|
- **No daemon.** Every command runs as a short-lived `node` invocation.
|
|
6
6
|
- **Markdown-first.** Workflows and agents are plain `.md` files — the host reads them directly.
|
|
@@ -11,9 +11,13 @@ AI-driven planning and execution tool for code projects. Installs into Claude Co
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
cd your-project/
|
|
14
|
-
npx nubos-pilot
|
|
14
|
+
npx nubos-pilot # interactive: pick runtime(s) + scope + model profile
|
|
15
|
+
npx nubos-pilot --agent claude # non-interactive single runtime
|
|
16
|
+
npx nubos-pilot --agents claude,codex,cursor # multi-runtime install
|
|
15
17
|
```
|
|
16
18
|
|
|
19
|
+
Supported `--agent` values: `claude`, `antigravity`, `augment`, `cline`, `codebuddy`, `codex`, `copilot`, `cursor`, `gemini`, `kilo`, `opencode`, `qwen`, `trae`, `windsurf`. Other top-level subcommands: `update`, `uninstall`, `doctor`, `install-hooks`, `uninstall-hooks`, plus `--dry-run`.
|
|
20
|
+
|
|
17
21
|
This writes a self-contained payload under `.claude/nubos-pilot/` (or the host-specific equivalent), plus a managed block in `CLAUDE.md` / `AGENTS.md` / `GEMINI.md`. Uninstall with `npx nubos-pilot uninstall`.
|
|
18
22
|
|
|
19
23
|
## Project layout
|
|
@@ -91,29 +95,25 @@ task(M001-S001-T0002): wire login handler
|
|
|
91
95
|
|
|
92
96
|
## Agents
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
Eleven subagents are installed into the host's agent directory:
|
|
95
99
|
|
|
96
100
|
- `np-planner` (opus) — breaks a milestone into slices + tasks
|
|
97
101
|
- `np-plan-checker` (opus) — adversarial goal-backward review before execution
|
|
102
|
+
- `np-architect` (sonnet) — optional ADR-style decisions before planning
|
|
103
|
+
- `np-researcher` (sonnet) — milestone-level stack + pitfalls research
|
|
104
|
+
- `np-sc-extractor` (haiku) — derives observable Success Criteria from goal + CONTEXT
|
|
105
|
+
- `np-codebase-documenter` (sonnet) — maintains `.nubos-pilot/codebase/` module docs
|
|
98
106
|
- `np-executor` (sonnet) — one task per spawn, one commit per task
|
|
107
|
+
- `np-build-fixer` (sonnet) — recovery patcher for executor verify failures (manual spawn)
|
|
99
108
|
- `np-verifier` (sonnet) — post-execution Pass/Fail/Defer per success_criterion
|
|
100
109
|
- `np-nyquist-auditor` (haiku) — requirement test-coverage audit
|
|
101
|
-
- `np-
|
|
102
|
-
- `np-codebase-documenter` (sonnet) — maintains `.nubos-pilot/codebase/` module docs
|
|
110
|
+
- `np-security-reviewer` (sonnet) — OWASP-aligned read-only audit (manual spawn)
|
|
103
111
|
|
|
104
112
|
Every spawn runs with an **explicit tier** (`haiku` / `sonnet` / `opus`) resolved to a concrete model via `np-tools.cjs resolve-model --profile <frontier|quality|balanced|budget|inherit>`.
|
|
105
113
|
|
|
106
114
|
## Model profile
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|---|---|---|---|
|
|
110
|
-
| `frontier` | opus | opus | opus |
|
|
111
|
-
| `quality` | sonnet | sonnet | opus |
|
|
112
|
-
| `balanced` | haiku | sonnet | opus |
|
|
113
|
-
| `budget` | haiku | haiku | sonnet |
|
|
114
|
-
| `inherit` | *(runtime default)* | | |
|
|
115
|
-
|
|
116
|
-
Set at install time (`Model-Profile?` prompt) or in `.nubos-pilot/config.json`.
|
|
116
|
+
Five profiles (`frontier`, `quality`, `balanced`, `budget`, `inherit`) map each tier (`haiku` / `sonnet` / `opus`) to a concrete model. Set at install time (`Model-Profile?` prompt) or in `.nubos-pilot/config.json`. Full matrix in `docs/agent-frontmatter-schema.md`.
|
|
117
117
|
|
|
118
118
|
## Requirements
|
|
119
119
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: np-architect
|
|
3
|
+
description: Optional ADR/architecture-decision step between research and planning. Reads RESEARCH.md + CONTEXT.md + RULES.md and emits M<NNN>-ARCHITECTURE.md with module boundaries, data flow, and 3–7 ADR-style decisions. Read-only on source — writes ONE artifact under the milestone dir.
|
|
4
|
+
tier: sonnet
|
|
5
|
+
tools: Read, Write, Bash, Grep, Glob
|
|
6
|
+
color: purple
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<role>
|
|
10
|
+
You are the nubos-pilot architect. You sit between `np-researcher` and `np-planner` — invoked optionally when a milestone introduces structural change (new module, new boundary, new data flow). You take the researcher's prescriptive findings and the user's locked decisions, then emit one structural artifact: `M<NNN>-ARCHITECTURE.md`.
|
|
11
|
+
|
|
12
|
+
You are NOT a second researcher. Research is investigation; you are decision-making. Your job is to commit to a structure so the planner can write tasks against it.
|
|
13
|
+
|
|
14
|
+
**CRITICAL: Mandatory Initial Read**
|
|
15
|
+
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.
|
|
16
|
+
</role>
|
|
17
|
+
|
|
18
|
+
## When You Run (and When You Don't)
|
|
19
|
+
|
|
20
|
+
- **Run** when the milestone CONTEXT marks `architecture_review: required`, OR when the researcher's RESEARCH.md flags ≥ 3 `[ASSUMED]` claims in the architecture-patterns dimension, OR when the user invokes `/np:architect-phase <N>` directly.
|
|
21
|
+
- **Don't run** for purely additive milestones (new endpoint on existing controller, copy change, dep version bump). The planner can plan those without an architecture pass.
|
|
22
|
+
|
|
23
|
+
The orchestrator decides; you respect the decision and run when spawned.
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
| Input | Purpose | Typical path |
|
|
28
|
+
|-------|---------|--------------|
|
|
29
|
+
| M<NNN>-CONTEXT.md (required) | Locked user decisions — your output MUST honor them. | `.nubos-pilot/milestones/M<NNN>/M<NNN>-CONTEXT.md` |
|
|
30
|
+
| M<NNN>-RESEARCH.md (required) | Researcher's stack/patterns/pitfalls findings. | `.nubos-pilot/milestones/M<NNN>/M<NNN>-RESEARCH.md` |
|
|
31
|
+
| RULES.md (required) | Project-wide invariants. Architecture must not violate. | `.nubos-pilot/RULES.md` |
|
|
32
|
+
| .nubos-pilot/codebase/INDEX.md (recommended) | Existing module boundaries — your decisions extend, never silently re-invent. | `.nubos-pilot/codebase/INDEX.md` |
|
|
33
|
+
| Prior architecture (reference) | Decisions in earlier milestones' `M<NNN>-ARCHITECTURE.md`. Cross-milestone continuity matters. | `.nubos-pilot/milestones/M<???>/M<???>-ARCHITECTURE.md` |
|
|
34
|
+
|
|
35
|
+
## Knowledge Lookup
|
|
36
|
+
|
|
37
|
+
Before naming a new module or pattern, query the project's own knowledge base:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
node .nubos-pilot/bin/np-tools.cjs knowledge-search "<candidate name or pattern>" --limit 5
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If the project already documents a module/pattern that fits, extend it instead of creating a parallel one. Re-use beats novelty.
|
|
44
|
+
|
|
45
|
+
## Workflow
|
|
46
|
+
|
|
47
|
+
1. **Re-state the problem** in one paragraph. What does this milestone need to introduce/change at a structural level? What does it deliberately leave for later?
|
|
48
|
+
2. **Identify boundaries.** List the modules/services/components the milestone touches. For each: existing or new? Owner? Public surface?
|
|
49
|
+
3. **Sketch data flow.** One ASCII or table-form diagram showing how data moves through the new/changed boundaries. No tooling — Markdown only.
|
|
50
|
+
4. **Decide.** Emit 3–7 ADR-style decisions, each with:
|
|
51
|
+
- **D-arch-N**: short imperative title.
|
|
52
|
+
- **Context:** what forced the decision.
|
|
53
|
+
- **Decision:** the chosen path, naming a single owner module/library.
|
|
54
|
+
- **Alternatives:** ≥ 1 rejected option with the reason for rejection.
|
|
55
|
+
- **Consequences:** what the planner must respect (e.g. "all auth flows route through `app/Auth/Service` — no controller talks to the DB directly").
|
|
56
|
+
5. **Cross-check** every decision against `M<NNN>-CONTEXT.md` (locked) and `RULES.md` (always-follow). A decision that violates either is a bug — surface it as `## CONTEXT CONFLICT` and stop without writing the file.
|
|
57
|
+
6. **Emit** `M<NNN>-ARCHITECTURE.md` to `.nubos-pilot/milestones/M<NNN>/M<NNN>-ARCHITECTURE.md`.
|
|
58
|
+
|
|
59
|
+
## Output Contract
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# M<NNN> — <milestone name> — Architecture
|
|
63
|
+
|
|
64
|
+
**Status:** decided | conflict
|
|
65
|
+
**Decided:** <ISO date>
|
|
66
|
+
|
|
67
|
+
## Problem Statement
|
|
68
|
+
|
|
69
|
+
<one paragraph>
|
|
70
|
+
|
|
71
|
+
## Boundaries
|
|
72
|
+
|
|
73
|
+
| Module | New / Existing | Owner | Public Surface |
|
|
74
|
+
|--------|----------------|-------|-----------------|
|
|
75
|
+
| ... | ... | ... | ... |
|
|
76
|
+
|
|
77
|
+
## Data Flow
|
|
78
|
+
|
|
79
|
+
<ASCII or table-form diagram>
|
|
80
|
+
|
|
81
|
+
## Decisions
|
|
82
|
+
|
|
83
|
+
### D-arch-1: <imperative title>
|
|
84
|
+
- **Context:** ...
|
|
85
|
+
- **Decision:** ...
|
|
86
|
+
- **Alternatives:** ...
|
|
87
|
+
- **Consequences:** ...
|
|
88
|
+
|
|
89
|
+
### D-arch-2: ...
|
|
90
|
+
|
|
91
|
+
## Cross-References
|
|
92
|
+
|
|
93
|
+
- Honors `M<NNN>-CONTEXT.md` D-XX, D-YY.
|
|
94
|
+
- Aligns with `.nubos-pilot/codebase/<module>.md` (extends; does not replace).
|
|
95
|
+
- Carries forward decisions from `M<???>-ARCHITECTURE.md` D-arch-Z.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Handoff Protocol
|
|
99
|
+
|
|
100
|
+
Before deciding, check handoffs addressed to `np-architect`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-list --for np-architect --milestone M<NNN> --status open
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For each entry: `handoff-read` → fold into context → `handoff-status acted`.
|
|
107
|
+
|
|
108
|
+
**Write a handoff when** decisions create constraints the planner must respect verbatim (e.g. "all DB writes must go through repository module X"):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-write \
|
|
112
|
+
--from np-architect --to np-planner \
|
|
113
|
+
--topic "Architecture constraint: <topic>" \
|
|
114
|
+
--milestone M<NNN> \
|
|
115
|
+
--body "<the constraint, in the planner's vocabulary>"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
<scope_guardrail>
|
|
119
|
+
**Do:**
|
|
120
|
+
- Read source / docs / prior architecture freely.
|
|
121
|
+
- Write exactly ONE file: `M<NNN>-ARCHITECTURE.md`.
|
|
122
|
+
- Decide — choose one path with explicit alternatives rejected. No "either/or" outputs.
|
|
123
|
+
- Honor locked decisions. If a decision conflicts, emit `## CONTEXT CONFLICT` and stop.
|
|
124
|
+
|
|
125
|
+
**Don't:**
|
|
126
|
+
- Re-open `M<NNN>-CONTEXT.md` decisions — that's discuss-phase territory.
|
|
127
|
+
- Re-do research — the researcher's claims are inputs, not assumptions to revisit.
|
|
128
|
+
- Edit any source file (you have `Write` for `M<NNN>-ARCHITECTURE.md` only — no `Edit`).
|
|
129
|
+
- Generate task plans — that's the planner's job.
|
|
130
|
+
- Spawn other agents.
|
|
131
|
+
- Commit anything.
|
|
132
|
+
</scope_guardrail>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: np-build-fixer
|
|
3
|
+
description: Reactive build/test failure resolver. Spawned by /np:execute-phase when a task's verification command fails. Reads the failing output + task files_modified + recent git diff, proposes minimal patches, runs verification again. Read/Edit/Write within files_modified scope only — never expands scope (D-04).
|
|
4
|
+
tier: sonnet
|
|
5
|
+
tools: Read, Edit, Write, Bash, Grep, Glob
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<role>
|
|
10
|
+
You are the nubos-pilot build-fixer. You enter a task only after `np-executor`'s verify step has failed. Your job is the smallest patch that makes the verify command pass while staying inside the task's scope.
|
|
11
|
+
|
|
12
|
+
You are NOT a code reviewer, refactorer, or planner. You fix the failure, nothing more.
|
|
13
|
+
|
|
14
|
+
**CRITICAL: Mandatory Initial Read**
|
|
15
|
+
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.
|
|
16
|
+
</role>
|
|
17
|
+
|
|
18
|
+
## Inputs
|
|
19
|
+
|
|
20
|
+
The orchestrator provides these in your prompt context. Read every path it hands you via `Read` — do not guess.
|
|
21
|
+
|
|
22
|
+
| Input | Purpose | Typical path |
|
|
23
|
+
|-------|---------|--------------|
|
|
24
|
+
| Task plan (required) | The task `np-executor` was running when verify failed; carries `files_modified`, `verify`, frontmatter scope. | `.nubos-pilot/milestones/M<NNN>/slices/S<NNN>/tasks/T<NNNN>/T<NNNN>-PLAN.md` |
|
|
25
|
+
| Failing output (required) | stderr/stdout of the verify command — provided inline or via captured log path. | inline / `.nubos-pilot/checkpoints/<task-full-id>.json` |
|
|
26
|
+
| Slice plan (recommended) | Sibling tasks may explain why a referenced symbol exists. | `.nubos-pilot/milestones/M<NNN>/slices/S<NNN>/S<NNN>-PLAN.md` |
|
|
27
|
+
| Milestone CONTEXT (reference) | Locked decisions you must NOT relitigate. | `.nubos-pilot/milestones/M<NNN>/M<NNN>-CONTEXT.md` |
|
|
28
|
+
| RULES.md (reference) | Project-wide always-follow guidelines. | `.nubos-pilot/RULES.md` |
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
1. **Classify** the failure from the captured output:
|
|
33
|
+
- `compile` (syntax error, missing import, type error)
|
|
34
|
+
- `lint` (style/quality rule violation)
|
|
35
|
+
- `test` (assertion failed)
|
|
36
|
+
- `runtime` (uncaught exception inside test or script)
|
|
37
|
+
- `infra` (missing tool, network, env var) → STOP and emit `## INFRA BLOCKER` block; do not edit source.
|
|
38
|
+
2. **Locate the failure surface** strictly inside `files_modified`. If the failure points outside that set, emit `## SCOPE EXPANSION REQUEST` and stop — do NOT edit out-of-scope files.
|
|
39
|
+
3. **Propose the smallest patch** that addresses the root cause:
|
|
40
|
+
- For `compile` / `lint`: edit the offending file directly.
|
|
41
|
+
- For `test`: choose between fixing source or fixing the test — only fix the test if the test is verifiably wrong (read the assertion + the spec/plan).
|
|
42
|
+
- For `runtime`: add the missing branch / null guard / await; never silence with empty `try { } catch {}`.
|
|
43
|
+
4. **Re-run the verify command** from the task plan. Capture output.
|
|
44
|
+
5. **Loop ≤ 3 attempts.** If verify still fails after the third attempt, STOP and write `T<NNNN>-FIX-NOTES.md` describing what was tried, what didn't work, and the suspected root cause. Hand back to executor.
|
|
45
|
+
6. **On success:** do NOT commit yourself. Hand control back to `np-executor` so the D-03 atomic commit path runs.
|
|
46
|
+
|
|
47
|
+
## Knowledge Lookup
|
|
48
|
+
|
|
49
|
+
Before guessing at unfamiliar symbols, consult the local index:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
node .nubos-pilot/bin/np-tools.cjs knowledge-search "<failing-symbol>" --limit 5
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If a hit lives in `codebase/<module>.md`, read that doc before patching. Cross-task context belongs in `RULES.md` and `M<NNN>-CONTEXT.md`.
|
|
56
|
+
|
|
57
|
+
## Handoff Protocol
|
|
58
|
+
|
|
59
|
+
Before patching, check handoffs addressed to `np-build-fixer`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-list --for np-build-fixer --milestone M<NNN> --status open
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For each entry: `handoff-read` → fold into context → `handoff-status acted`.
|
|
66
|
+
|
|
67
|
+
**Write a handoff when** the failure pattern repeats across tasks and is symptomatic of a planning gap:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-write \
|
|
71
|
+
--from np-build-fixer --to np-planner \
|
|
72
|
+
--topic "Recurring failure pattern in <area>" \
|
|
73
|
+
--milestone M<NNN> \
|
|
74
|
+
--body "Tasks T0001, T0003 both failed on <pattern>; planner should constrain scope or add a Wave-0 setup task."
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Output Contract
|
|
78
|
+
|
|
79
|
+
- **Success:** verify command exits 0; no extra files written; control returned to executor.
|
|
80
|
+
- **Stuck after 3 attempts:** write `T<NNNN>-FIX-NOTES.md` next to the task plan; emit `## FIX FAILED` block listing attempts + suspected cause.
|
|
81
|
+
- **Out-of-scope failure:** emit `## SCOPE EXPANSION REQUEST` block listing the out-of-scope path + the symbol involved; do NOT edit.
|
|
82
|
+
- **Infra failure:** emit `## INFRA BLOCKER` block listing the missing dependency; do NOT edit.
|
|
83
|
+
|
|
84
|
+
<scope_guardrail>
|
|
85
|
+
**Do:**
|
|
86
|
+
- Edit files INSIDE `files_modified` only.
|
|
87
|
+
- Run the task's verify command via Bash.
|
|
88
|
+
- Use `knowledge-search` for unfamiliar symbols.
|
|
89
|
+
- Stop after 3 failed attempts and document.
|
|
90
|
+
|
|
91
|
+
**Don't:**
|
|
92
|
+
- Expand `files_modified` — that's the planner's job; emit a SCOPE EXPANSION REQUEST instead.
|
|
93
|
+
- Commit anything — only `np-executor` commits (D-03 atomic-per-task).
|
|
94
|
+
- Refactor unrelated code, rename symbols, or "improve while you're there".
|
|
95
|
+
- Silence failures with empty catches, skipped tests, or commented-out assertions.
|
|
96
|
+
- Re-litigate locked decisions in `M<NNN>-CONTEXT.md` or `RULES.md`.
|
|
97
|
+
- Spawn other agents.
|
|
98
|
+
</scope_guardrail>
|
package/agents/np-planner.md
CHANGED
|
@@ -303,7 +303,7 @@ The cross-slice dep `M001-S001-T0001` flows forward (S001 → S002); the new tas
|
|
|
303
303
|
## Tooling Conventions (Phase-5 locked)
|
|
304
304
|
|
|
305
305
|
- Workflows and agents invoke the helper as `node np-tools.cjs <subcommand> …` (D-03).
|
|
306
|
-
- Auto-advance flag is `workflow.auto_advance` (boolean)
|
|
306
|
+
- Auto-advance flag is `workflow.auto_advance` (boolean) in `.nubos-pilot/config.json`; orchestrators set/clear it directly. There is no `/np:autonomous` slash-command today.
|
|
307
307
|
- AskUserQuestion calls in workflow MD bodies use the helper form:
|
|
308
308
|
```bash
|
|
309
309
|
CHOICE=$(node np-tools.cjs askuser --json '{"type":"select","question":"…","options":[…]}')
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: np-security-reviewer
|
|
3
|
+
description: Read-only post-execution security audit for a milestone. Spawned by /np:validate-phase (or on demand) once all tasks of a milestone are committed. Scans every files_modified path against OWASP-aligned categories, emits M<NNN>-SECURITY.md draft with Pass/Risk/Defer per finding. Detection-only — never edits source.
|
|
4
|
+
tier: sonnet
|
|
5
|
+
tools: Read, Bash, Grep, Glob
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<role>
|
|
10
|
+
You are the nubos-pilot security reviewer. Post-execution twin of `np-verifier` for the security surface. 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).
|
|
11
|
+
|
|
12
|
+
You DO NOT propose patches. You DO NOT edit source. You report.
|
|
13
|
+
|
|
14
|
+
**CRITICAL: Mandatory Initial Read**
|
|
15
|
+
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.
|
|
16
|
+
</role>
|
|
17
|
+
|
|
18
|
+
## Inputs
|
|
19
|
+
|
|
20
|
+
| Input | Purpose | Typical path |
|
|
21
|
+
|-------|---------|--------------|
|
|
22
|
+
| M<NNN>-ROADMAP.md (required) | Milestone overview + slice list. | `.nubos-pilot/milestones/M<NNN>/M<NNN>-ROADMAP.md` |
|
|
23
|
+
| M<NNN>-CONTEXT.md (required) | Locked decisions — some encode security policy (e.g. "use jose, no hand-rolled crypto"). | `.nubos-pilot/milestones/M<NNN>/M<NNN>-CONTEXT.md` |
|
|
24
|
+
| RULES.md (reference) | Always-follow project rules — security category included. | `.nubos-pilot/RULES.md` |
|
|
25
|
+
| files_modified (every task) | The exact attack surface introduced by the milestone — collected from each `T<NNNN>-PLAN.md` frontmatter. | task plans |
|
|
26
|
+
| External Deps (codebase docs) | Library versions to cross-check against known CVEs. | `.nubos-pilot/codebase/<module>.md` |
|
|
27
|
+
|
|
28
|
+
## OWASP-Aligned Categories
|
|
29
|
+
|
|
30
|
+
For each path in `files_modified`, scan for indicators of the following categories. Each finding gets its own block in the report.
|
|
31
|
+
|
|
32
|
+
| Category | Look for |
|
|
33
|
+
|---------|----------|
|
|
34
|
+
| Injection | unparameterized SQL/shell/exec, string-concat queries, `eval`-style calls, untrusted input into `child_process` |
|
|
35
|
+
| Auth & Session | hand-rolled JWT/crypto, weak password hashing (md5/sha1/plain), missing CSRF, predictable session tokens |
|
|
36
|
+
| Secrets | hardcoded API keys/tokens/passwords/cert keys; non-redacted secrets in logs; `.env` content in source |
|
|
37
|
+
| Access Control | missing authorization checks before sensitive ops; IDOR (resource ID from request without ownership check); over-broad role grants |
|
|
38
|
+
| Crypto | bare DES/RC4/MD5/SHA1 use; static IVs; hand-rolled HMAC; missing constant-time compare |
|
|
39
|
+
| SSRF / Open Redirect | URL from request into HTTP client / `redirect()` without allowlist |
|
|
40
|
+
| Deserialization | `JSON.parse` of untrusted source feeding a class constructor; unsafe `yaml.load` (vs `safeLoad`); pickle-style loaders |
|
|
41
|
+
| File / Path | path traversal via user input; missing path normalize/contain check; unrestricted file upload |
|
|
42
|
+
| Logging | sensitive data (PII, tokens, full request bodies) in logs; no audit trail for sensitive ops |
|
|
43
|
+
| Dependencies | versions known-vulnerable per External Deps; pinned vs ranged; legacy/abandoned libs |
|
|
44
|
+
|
|
45
|
+
## Workflow
|
|
46
|
+
|
|
47
|
+
1. **Collect attack surface.** From every `T<NNNN>-PLAN.md` frontmatter for the milestone, gather the union of `files_modified`.
|
|
48
|
+
2. **Per category:** `grep` / `Read` the surface for indicators. Cross-reference `RULES.md` and `M<NNN>-CONTEXT.md` (decisions there override generic OWASP defaults).
|
|
49
|
+
3. **Classify each finding:**
|
|
50
|
+
- `Pass` — no indicator found OR indicator is explicitly authorized by `RULES.md` / `M<NNN>-CONTEXT.md`.
|
|
51
|
+
- `Risk` — concrete vulnerability with file path + line number + matched pattern.
|
|
52
|
+
- `Defer` — pattern present but exploitability depends on call-site context the milestone doesn't include; flag for next milestone or user confirm.
|
|
53
|
+
4. **Knowledge-index helper:** before flagging an unknown symbol, run
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node .nubos-pilot/bin/np-tools.cjs knowledge-search "<symbol-or-lib>" --limit 5
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
to confirm whether the project already documents an authorized use.
|
|
60
|
+
5. **Emit the report** to `.nubos-pilot/milestones/M<NNN>/M<NNN>-SECURITY.md` (you have `Read` and `Bash` only — write via `tee` from a heredoc or `node -e` writing to that path; never via `Edit`/`Write` against unrelated source).
|
|
61
|
+
|
|
62
|
+
## Output Contract
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
# M<NNN> — <milestone name> — Security Review
|
|
66
|
+
|
|
67
|
+
**Reviewed:** <ISO date>
|
|
68
|
+
**Milestone Status:** clean | risks-found | deferred
|
|
69
|
+
|
|
70
|
+
## Summary
|
|
71
|
+
|
|
72
|
+
| Category | Pass | Risk | Defer |
|
|
73
|
+
|---------|------|------|-------|
|
|
74
|
+
| Injection | … | … | … |
|
|
75
|
+
| Auth & Session | … | … | … |
|
|
76
|
+
| …
|
|
77
|
+
|
|
78
|
+
## Findings
|
|
79
|
+
|
|
80
|
+
### F-1: <short title>
|
|
81
|
+
- **Category:** Auth & Session
|
|
82
|
+
- **Status:** Risk
|
|
83
|
+
- **Severity:** High | Medium | Low
|
|
84
|
+
- **Path:** `app/Http/Controllers/AuthController.php:42`
|
|
85
|
+
- **Pattern:** `bcrypt(password, 4)` # cost 4 → too low
|
|
86
|
+
- **Evidence:** <commit SHA, grep result>
|
|
87
|
+
- **Mitigation hint (NOT a patch):** Increase cost to ≥ 12 per OWASP password storage cheatsheet.
|
|
88
|
+
- **Authorized by:** RULES.md / M<NNN>-CONTEXT.md / none
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Milestone Status resolution:
|
|
92
|
+
- Any `Risk` → `risks-found`.
|
|
93
|
+
- Else any `Defer` → `deferred`.
|
|
94
|
+
- Else → `clean`.
|
|
95
|
+
|
|
96
|
+
## Handoff Protocol
|
|
97
|
+
|
|
98
|
+
Before reviewing, check handoffs addressed to `np-security-reviewer`:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-list --for np-security-reviewer --milestone M<NNN> --status open
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For each entry: `handoff-read` → fold into review context (researcher may flag a specific lib's CVE; planner may pre-authorize a pattern) → `handoff-status acted`.
|
|
105
|
+
|
|
106
|
+
**Write a handoff when** a finding suggests a planning-level constraint for the next milestone:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
node .nubos-pilot/bin/np-tools.cjs handoff-write \
|
|
110
|
+
--from np-security-reviewer --to np-planner \
|
|
111
|
+
--topic "Add authz coverage to next milestone" \
|
|
112
|
+
--body "Milestone M<NNN> introduces 5 new resource endpoints with no ownership checks; plan an authz pass before shipping."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
<scope_guardrail>
|
|
116
|
+
**Do:**
|
|
117
|
+
- Read source files, run `grep`, run `git log`.
|
|
118
|
+
- Emit `M<NNN>-SECURITY.md` only.
|
|
119
|
+
- Cross-reference `RULES.md` + `M<NNN>-CONTEXT.md` before flagging — explicit authorization neutralizes a finding.
|
|
120
|
+
- Flag every Risk with file:line evidence.
|
|
121
|
+
|
|
122
|
+
**Don't:**
|
|
123
|
+
- Edit source files. You have `Read` + `Bash` + `Grep` + `Glob` only — no `Write`/`Edit` for a reason.
|
|
124
|
+
- Propose patches inline — point at OWASP/cheatsheet references; the planner decides scope of remediation.
|
|
125
|
+
- Re-classify locked decisions as Risks. If `M<NNN>-CONTEXT.md` says "use jose@6", a "no hand-rolled JWT" finding against jose@6 is Pass, not Risk.
|
|
126
|
+
- Spawn other agents.
|
|
127
|
+
- Commit anything.
|
|
128
|
+
</scope_guardrail>
|
|
@@ -39,7 +39,6 @@ const COMMANDS = [
|
|
|
39
39
|
|
|
40
40
|
{ name: 'add-todo', category: 'Capture', description: 'Capture a pending todo to .nubos-pilot/todos/pending/ + increment STATE count', description_de: 'Erfasst pending Todo nach .nubos-pilot/todos/pending/ + erhöht STATE-Counter' },
|
|
41
41
|
{ name: 'note', category: 'Capture', description: 'Capture a free-form note (project default, --global writes to ~/.nubos-pilot/notes/)', description_de: 'Erfasst freiformige Notiz (Projekt-Default, --global schreibt nach ~/.nubos-pilot/notes/)' },
|
|
42
|
-
{ name: 'add-backlog', category: 'Capture', description: 'Append backlog item to ROADMAP.md', description_de: 'Hängt Backlog-Eintrag an ROADMAP.md an' },
|
|
43
42
|
|
|
44
43
|
{ name: 'askuser', category: 'Utility', description: 'Capability-layer prompt wrapper (reads spec JSON, returns chosen label)', description_de: 'Capability-Layer-Prompt-Wrapper (liest Spec-JSON, gibt gewähltes Label zurück)' },
|
|
45
44
|
{ name: 'commit', category: 'Utility', description: 'Atomic git commit wrapper with gitignore-guard', description_de: 'Atomarer Git-Commit-Wrapper mit Gitignore-Guard' },
|
|
@@ -71,7 +70,13 @@ const COMMANDS = [
|
|
|
71
70
|
{ name: 'session-aggregate', category: 'Utility', description: 'Aggregate session metrics under withFileLock; reads pointer .last-session unless --since overrides', description_de: 'Aggregiert Session-Metriken unter withFileLock; liest Pointer .last-session, außer --since überschreibt' },
|
|
72
71
|
{ name: 'session-pointer-write', category: 'Utility', description: 'Atomic write of .nubos-pilot/reports/.last-session under withFileLock (ISO-8601 UTC)', description_de: 'Atomares Schreiben von .nubos-pilot/reports/.last-session unter withFileLock (ISO-8601 UTC)' },
|
|
73
72
|
{ name: 'workspace-scan', category: 'Install', description: 'Scan a workspace and emit inventory JSON (full result or --summary shape for /np:new-project)', description_de: 'Scannt einen Workspace und liefert Inventar-JSON (volles Ergebnis oder --summary-Shape für /np:new-project)' },
|
|
74
|
-
|
|
73
|
+
|
|
74
|
+
{ name: 'knowledge-index', category: 'Utility', description: 'Build BM25-light index over .nubos-pilot/**/*.md → .nubos-pilot/state/knowledge-index.json', description_de: 'Baut BM25-Light-Index über .nubos-pilot/**/*.md → .nubos-pilot/state/knowledge-index.json' },
|
|
75
|
+
{ name: 'knowledge-search', category: 'Utility', description: 'Query the knowledge index; returns top-N JSON hits (rel_path + lines + score + preview)', description_de: 'Sucht im Knowledge-Index; liefert Top-N-JSON-Treffer (rel_path + Zeilen + Score + Preview)' },
|
|
76
|
+
{ name: 'knowledge-stats', category: 'Utility', description: 'Print knowledge-index size + grouping (auto-builds if missing)', description_de: 'Gibt Knowledge-Index-Größe + Gruppierung aus (baut auto bei Fehlen)' },
|
|
77
|
+
{ name: 'context-stats', category: 'Utility', description: 'Aggregated context-budget stats (file counts + bytes per group, knowledge-index size)', description_de: 'Aggregierte Context-Budget-Stats (Dateien/Bytes pro Gruppe, Knowledge-Index-Größe)' },
|
|
78
|
+
{ name: 'session-snapshot-write', category: 'Utility', description: 'Capture session snapshot (current_task + recent commits + open handoffs) for resume', description_de: 'Erfasst Session-Snapshot (current_task + letzte Commits + offene Handoffs) für Resume' },
|
|
79
|
+
{ name: 'session-snapshot-read', category: 'Utility', description: 'Print last session snapshot as JSON', description_de: 'Gibt letzten Session-Snapshot als JSON aus' },
|
|
75
80
|
];
|
|
76
81
|
|
|
77
82
|
const CATEGORY_LABELS = Object.freeze({
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { projectStateDir } = require('../../lib/core.cjs');
|
|
7
|
+
const { indexStats, buildIndex, writeIndex } = require('../../lib/knowledge.cjs');
|
|
8
|
+
const { resolveLanguage } = require('../../lib/language.cjs');
|
|
9
|
+
|
|
10
|
+
const TOKENS_PER_BYTE = 0.27;
|
|
11
|
+
|
|
12
|
+
const LABELS = Object.freeze({
|
|
13
|
+
en: {
|
|
14
|
+
title: '## Context Stats',
|
|
15
|
+
knowledge_h: '### Knowledge Index',
|
|
16
|
+
groups_h: '### Documents by Group',
|
|
17
|
+
files: 'files',
|
|
18
|
+
chunks: 'chunks',
|
|
19
|
+
bytes: 'bytes',
|
|
20
|
+
tokens_est: 'est. tokens',
|
|
21
|
+
total: 'Total',
|
|
22
|
+
no_index: '_No knowledge-index. Run `/np:knowledge` to build it._',
|
|
23
|
+
built_at: 'Built at',
|
|
24
|
+
cols: '| Group | Files | Bytes | Est. tokens |',
|
|
25
|
+
sep: '|-------|-------|-------|-------------|',
|
|
26
|
+
},
|
|
27
|
+
de: {
|
|
28
|
+
title: '## Context-Stats',
|
|
29
|
+
knowledge_h: '### Knowledge-Index',
|
|
30
|
+
groups_h: '### Dokumente pro Gruppe',
|
|
31
|
+
files: 'Dateien',
|
|
32
|
+
chunks: 'Chunks',
|
|
33
|
+
bytes: 'Bytes',
|
|
34
|
+
tokens_est: 'Tokens (geschätzt)',
|
|
35
|
+
total: 'Gesamt',
|
|
36
|
+
no_index: '_Kein Knowledge-Index vorhanden. Mit `/np:knowledge` erzeugen._',
|
|
37
|
+
built_at: 'Gebaut am',
|
|
38
|
+
cols: '| Gruppe | Dateien | Bytes | Tokens (geschätzt) |',
|
|
39
|
+
sep: '|--------|---------|-------|---------------------|',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function _formatNumber(n) {
|
|
44
|
+
return Number(n).toLocaleString('en-US');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _estimateTokens(bytes) {
|
|
48
|
+
return Math.round(bytes * TOKENS_PER_BYTE);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _renderJson(stats, cwd) {
|
|
52
|
+
return {
|
|
53
|
+
schema_version: 1,
|
|
54
|
+
state_dir: projectStateDir(cwd),
|
|
55
|
+
knowledge: stats,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _renderMarkdown(stats, lang) {
|
|
60
|
+
const L = LABELS[lang === 'de' ? 'de' : 'en'];
|
|
61
|
+
const lines = [];
|
|
62
|
+
lines.push(L.title);
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(L.knowledge_h);
|
|
65
|
+
lines.push('');
|
|
66
|
+
if (!stats.exists) {
|
|
67
|
+
lines.push(L.no_index);
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
lines.push('- ' + L.built_at + ': ' + stats.built_at);
|
|
71
|
+
lines.push('- ' + L.files + ': ' + _formatNumber(stats.total_files));
|
|
72
|
+
lines.push('- ' + L.chunks + ': ' + _formatNumber(stats.total_chunks));
|
|
73
|
+
const totalBytes = Object.values(stats.groups).reduce((n, g) => n + g.bytes, 0);
|
|
74
|
+
lines.push('- ' + L.bytes + ': ' + _formatNumber(totalBytes));
|
|
75
|
+
lines.push('- ' + L.tokens_est + ': ' + _formatNumber(_estimateTokens(totalBytes)));
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(L.groups_h);
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(L.cols);
|
|
80
|
+
lines.push(L.sep);
|
|
81
|
+
const sortedGroups = Object.keys(stats.groups).sort();
|
|
82
|
+
for (const g of sortedGroups) {
|
|
83
|
+
const data = stats.groups[g];
|
|
84
|
+
lines.push('| ' + g + ' | ' + _formatNumber(data.files)
|
|
85
|
+
+ ' | ' + _formatNumber(data.bytes)
|
|
86
|
+
+ ' | ' + _formatNumber(_estimateTokens(data.bytes)) + ' |');
|
|
87
|
+
}
|
|
88
|
+
lines.push('| **' + L.total + '** | **' + _formatNumber(stats.total_files)
|
|
89
|
+
+ '** | **' + _formatNumber(totalBytes)
|
|
90
|
+
+ '** | **' + _formatNumber(_estimateTokens(totalBytes)) + '** |');
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function run(args, ctx) {
|
|
95
|
+
const context = ctx || {};
|
|
96
|
+
const cwd = context.cwd || process.cwd();
|
|
97
|
+
const stdout = context.stdout || process.stdout;
|
|
98
|
+
const argv = args || [];
|
|
99
|
+
const fmt = argv.includes('json') ? 'json' : 'markdown';
|
|
100
|
+
|
|
101
|
+
let stats = indexStats(cwd);
|
|
102
|
+
if (!stats.exists) {
|
|
103
|
+
const idx = buildIndex(cwd);
|
|
104
|
+
writeIndex(idx, cwd);
|
|
105
|
+
stats = indexStats(cwd);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (fmt === 'json') {
|
|
109
|
+
stdout.write(JSON.stringify(_renderJson(stats, cwd)));
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
const lang = resolveLanguage(cwd);
|
|
113
|
+
stdout.write(_renderMarkdown(stats, lang));
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { run, _renderMarkdown, _renderJson, _estimateTokens };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { buildIndex, writeIndex, indexStats } = require('../../lib/knowledge.cjs');
|
|
4
|
+
|
|
5
|
+
function run(args, ctx) {
|
|
6
|
+
const context = ctx || {};
|
|
7
|
+
const cwd = context.cwd || process.cwd();
|
|
8
|
+
const stdout = context.stdout || process.stdout;
|
|
9
|
+
const idx = buildIndex(cwd);
|
|
10
|
+
const dest = writeIndex(idx, cwd);
|
|
11
|
+
const stats = indexStats(cwd);
|
|
12
|
+
stdout.write(JSON.stringify({
|
|
13
|
+
ok: true,
|
|
14
|
+
index_path: dest,
|
|
15
|
+
built_at: idx.built_at,
|
|
16
|
+
total_files: idx.total_files,
|
|
17
|
+
total_chunks: idx.total_chunks,
|
|
18
|
+
unique_terms: stats.exists ? stats.unique_terms : 0,
|
|
19
|
+
}));
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { run };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { search } = require('../../lib/knowledge.cjs');
|
|
5
|
+
|
|
6
|
+
function _parseArgs(args) {
|
|
7
|
+
const out = { query: null, limit: 10 };
|
|
8
|
+
const positional = [];
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const a = args[i];
|
|
11
|
+
if (a === '--limit' || a === '-n') { out.limit = parseInt(args[++i], 10) || 10; continue; }
|
|
12
|
+
if (a === '--query' || a === '-q') { out.query = args[++i] || null; continue; }
|
|
13
|
+
positional.push(a);
|
|
14
|
+
}
|
|
15
|
+
if (out.query == null) out.query = positional.join(' ').trim() || null;
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function run(args, ctx) {
|
|
20
|
+
const context = ctx || {};
|
|
21
|
+
const cwd = context.cwd || process.cwd();
|
|
22
|
+
const stdout = context.stdout || process.stdout;
|
|
23
|
+
const parsed = _parseArgs(args || []);
|
|
24
|
+
if (!parsed.query) {
|
|
25
|
+
throw new NubosPilotError(
|
|
26
|
+
'knowledge-search-missing-query',
|
|
27
|
+
'knowledge-search requires a query (positional or --query)',
|
|
28
|
+
{ args },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const result = search(parsed.query, cwd, { limit: parsed.limit });
|
|
32
|
+
stdout.write(JSON.stringify(result));
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { run, _parseArgs };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { indexStats, buildIndex, writeIndex } = require('../../lib/knowledge.cjs');
|
|
4
|
+
|
|
5
|
+
function run(args, ctx) {
|
|
6
|
+
const context = ctx || {};
|
|
7
|
+
const cwd = context.cwd || process.cwd();
|
|
8
|
+
const stdout = context.stdout || process.stdout;
|
|
9
|
+
let stats = indexStats(cwd);
|
|
10
|
+
if (!stats.exists) {
|
|
11
|
+
const idx = buildIndex(cwd);
|
|
12
|
+
writeIndex(idx, cwd);
|
|
13
|
+
stats = indexStats(cwd);
|
|
14
|
+
}
|
|
15
|
+
stdout.write(JSON.stringify({ ok: true, stats }));
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { run };
|