wize-dev-kit 0.4.1 → 0.6.0

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/package.json +1 -1
  3. package/src/core-skills/wize-customize/skill.md +114 -0
  4. package/src/core-skills/wize-editorial-review-prose/skill.md +92 -0
  5. package/src/core-skills/wize-editorial-review-structure/skill.md +97 -0
  6. package/src/core-skills/wize-index-docs/skill.md +117 -0
  7. package/src/core-skills/wize-review-edge-case-hunter/skill.md +112 -0
  8. package/src/method-skills/2-plan-workflows/wize-edit-prd/workflow.md +108 -0
  9. package/src/method-skills/3-solutioning/wize-project-context/workflow.md +118 -0
  10. package/src/method-skills/4-implementation/wize-checkpoint-preview/workflow.md +115 -0
  11. package/src/method-skills/4-implementation/wize-correct-course/workflow.md +89 -0
  12. package/src/method-skills/4-implementation/wize-investigate/workflow.md +121 -0
  13. package/src/method-skills/4-implementation/wize-sprint-planning/workflow.md +58 -71
  14. package/src/method-skills/4-implementation/wize-sprint-status/workflow.md +29 -82
  15. package/src/orchestrator-skills/wize-onboarding/workflow.md +76 -14
  16. package/src/security-overlay/_shared/allowlist.js +154 -0
  17. package/src/security-overlay/_shared/cli-runner.js +87 -0
  18. package/src/security-overlay/_shared/cvss.js +108 -0
  19. package/src/security-overlay/_shared/detect.js +125 -0
  20. package/src/security-overlay/_shared/install-script.js +205 -0
  21. package/src/security-overlay/_shared/invoke-phase.js +86 -0
  22. package/src/security-overlay/_shared/owasp.js +56 -0
  23. package/src/security-overlay/_shared/partial.js +225 -0
  24. package/src/security-overlay/_shared/preflight.js +175 -0
  25. package/src/security-overlay/_shared/scope-gate.js +172 -0
  26. package/src/security-overlay/_shared/scope-parser.js +120 -0
  27. package/src/security-overlay/agents/red-teamer/agent.yaml +51 -0
  28. package/src/security-overlay/agents/red-teamer/persona.md +43 -0
  29. package/src/security-overlay/data/common.txt +115 -0
  30. package/src/security-overlay/data/owasp-top10.json +15 -0
  31. package/src/security-overlay/data/tool-allowlist.json +31 -0
  32. package/src/security-overlay/skills/wize-sec-enumerate/scripts/run-enumerate.js +180 -0
  33. package/src/security-overlay/skills/wize-sec-enumerate/skill.md +32 -0
  34. package/src/security-overlay/skills/wize-sec-exploit/data/common.txt +117 -0
  35. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-ffuf.js +147 -0
  36. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-nikto.js +145 -0
  37. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-nuclei.js +176 -0
  38. package/src/security-overlay/skills/wize-sec-exploit/scripts/run-sqlmap.js +139 -0
  39. package/src/security-overlay/skills/wize-sec-pentest/scripts/run-pipeline.js +157 -0
  40. package/src/security-overlay/skills/wize-sec-pentest/skill.md +52 -0
  41. package/src/security-overlay/skills/wize-sec-recon/scripts/run-gitleaks.js +139 -0
  42. package/src/security-overlay/skills/wize-sec-recon/scripts/run-osv.js +227 -0
  43. package/src/security-overlay/skills/wize-sec-recon/scripts/run-recon.js +162 -0
  44. package/src/security-overlay/skills/wize-sec-recon/skill.md +35 -0
  45. package/src/security-overlay/skills/wize-sec-report/scripts/render-report.js +999 -0
  46. package/src/tea-skills/wize-qa-generate-e2e-tests/workflow.md +119 -0
  47. package/tools/installer/onboarding.js +1 -0
  48. package/tools/installer/render-shared.js +50 -3
  49. package/tools/installer/wize-cli.js +72 -5
@@ -8,92 +8,79 @@ status: ready
8
8
 
9
9
  # Sprint Planning
10
10
 
11
- **Goal.** Pick what enters this sprint. Capacity-honest, priority-honest, risk-honest. The sprint is a commitment about a small slice of the future, not a wish list.
11
+ **Goal.** Pick what enters this sprint. Capacity-honest, priority-honest, risk-honest.
12
12
 
13
- Maria Hill chairs. Tony advises on slicing. Hawkeye flags risk on stories. Shuri commits to the load.
13
+ Maria Hill chairs. Tony advises on slicing. Hawkeye flags risk. Shuri commits to the load.
14
14
 
15
15
  ## Inputs
16
16
 
17
17
  - Story backlog: `.wize/solutioning/stories/`
18
- - Velocity history: `.wize/implementation/sprint-status.md` (previous sprints) — when present.
18
+ - Previous sprint state: `.wize/implementation/sprint-status.yaml`
19
19
  - `.wize/implementation/tea/risk-profile.md`
20
- - Open `tea-gate` outcomes from last sprint.
21
- - Team availability for the next interval (vacations, on-call rotation, planned meetings).
20
+ - Team availability for the next interval.
22
21
 
23
22
  ## Output
24
23
 
25
- - New sprint block appended to `.wize/implementation/sprint-status.md`.
26
- - Story files updated `priority: 1` for chosen stories.
27
-
28
- ## Rules
29
-
30
- 1. **Capacity = min(history velocity, declared availability).** Not the average of optimistic estimates.
31
- 2. **High-risk stories** (linked to `R-x` HIGH in risk profile) get TEA design done in the planning meeting, not at story start.
32
- 3. **Stretch goals** are explicit, named, not silent. If a stretch ships, great. If not, the sprint isn't a failure.
33
- 4. **Don't carry over without reason.** A carried-over story gets a one-line "why" in the sprint log.
24
+ - Updated `.wize/implementation/sprint-status.yaml`.
25
+ - Story files updated with `priority: 1` for chosen stories.
34
26
 
35
27
  ## Steps
36
28
 
37
- ### 1. Look back (3 min)
38
-
39
- Last sprint: what shipped, what slipped, what surprised. Don't relitigate; observe.
40
-
41
- ### 2. Refresh capacity
42
-
43
- - Person-days available this sprint = sum(working days) × (1 - meetings load).
44
- - Subtract on-call burden, oncall handoff time, planned reviews.
45
-
46
- ### 3. Pull stories (in priority order)
47
-
48
- Default selection algorithm:
49
- - Always pull continuation stories (in-flight from last sprint) first.
50
- - Then highest-priority stories that fit the capacity.
51
- - Then risk-driven: high-risk stories (R-HIGH) preferred over more low-risk ones when capacity is tight.
52
-
53
- For each pulled story, confirm INVEST still holds; re-slice if needed.
54
-
55
- ### 4. Reserve buffer
56
-
57
- 10–15% buffer for unknowns (bug fixes, support escalations). Don't fill the sprint to 100% — you'll always pay.
58
-
59
- ### 5. Walk the gate plan
60
-
61
- For each story pulled, what's the TEA gate cadence? Most stories: design at start, trace + review + gate at end. High-risk: include NFR re-check at epic close.
62
-
63
- ### 6. Commit (verbal + written)
64
-
65
- Each engineer reads back the stories they're owning. Hill writes them into the sprint block. Sprint starts.
66
-
67
- ## Sprint block template (appended to `sprint-status.md`)
68
-
69
- ```markdown
70
- ## Sprint 7 — 2026-06-12 → 2026-06-25
71
-
72
- **Capacity:** 24 person-days (3 engineers × 10 days × 0.8 utilization)
73
- **Carry-over:** E01-S05 (90% done; Shuri); E03-S01 (TEA review pending)
74
- **Pulled:**
75
- - E01-S06 — M — owner: Shuri — gate cadence: design+gate
76
- - E02-S02 — L — owner: Shuri — gate cadence: design+trace+review+gate (R-3)
77
- - E03-S02 — M — owner: Aaliyah — gate cadence: design+trace+review+gate (R-1)
78
- - E04-S01 — S — owner: Shuri — gate cadence: smoke (quick-dev pattern)
79
- - E02-S03 — S — stretch
80
-
81
- **Out (deferred to Sprint 8):**
82
- - E03-S03 — reason: depends on E03-S02 ADR
83
- - E05-S01 — reason: out of NFR-cost budget; revisit
84
-
85
- **Risks flagged:**
86
- - E02-S02 — auth refresh story; high-risk; TEA design done at planning.
29
+ 1. **Look back** what shipped, what slipped, what surprised.
30
+ 2. **Refresh capacity** — person-days × utilization − overhead.
31
+ 3. **Pull stories** continuation first, then priority, then risk.
32
+ 4. **Reserve 10–15% buffer** for unknowns.
33
+ 5. **Walk the gate plan** — design/trace/review/gate per story.
34
+ 6. **Commit** — verbal + written into YAML.
35
+
36
+ ## Status state machine
37
+
38
+ - Epic: `backlog` `in-progress` `done`
39
+ - Story: `backlog` → `ready-for-dev` → `in-progress` → `review` → `done`
40
+ - Retrospective: `optional` ↔ `done`
41
+
42
+ ## Sprint block template
43
+
44
+ ```yaml
45
+ # generated: YYYY-MM-DD
46
+ # last_updated: YYYY-MM-DD
47
+ # project: {project_name}
48
+ # project_key: {project_key}
49
+ # tracking_system: file-system
50
+ # story_location: .wize/solutioning/stories
51
+
52
+ generated: YYYY-MM-DD
53
+ last_updated: YYYY-MM-DD
54
+ project: {project_name}
55
+ project_key: {project_key}
56
+ tracking_system: file-system
57
+ story_location: .wize/solutioning/stories
58
+
59
+ development_status:
60
+ epic-1: backlog
61
+ 1-1-story-one: backlog
62
+ 1-2-story-two: backlog
63
+ epic-1-retrospective: optional
87
64
  ```
88
65
 
89
- ## Anti-patterns Hill rejects
66
+ ## Anti-patterns
90
67
 
91
- - **"Optimistic" velocity that ignores history.** Use observed velocity.
92
- - **Stories pulled without owners.** Don't aspire; commit.
93
- - **Stretch goals so big they're really plan.** Stretch = optional, not "we hope we can."
94
- - **Pulling a story when its dependency hasn't shipped.** It will sit blocked.
95
- - **No buffer.** Real sprints have surprises.
68
+ - Optimistic velocity.
69
+ - Stories without owners.
70
+ - Stretch goals that are really plan.
71
+ - Pulling blocked dependencies.
72
+ - Zero buffer.
96
73
 
97
74
  ## Hand-off
98
75
 
99
- > Sprint 7 committed at `.wize/implementation/sprint-status.md`. Shuri owns most; Aaliyah picks up E03-S02. Hawkeye, NFR gate due on E03 at sprint end. Wizer, retro on the 25th.
76
+ > Sprint committed at `.wize/implementation/sprint-status.yaml`. Stories in `ready-for-dev` are now eligible for the dev loop.
77
+ >
78
+ > **Recommended next loop:**
79
+ >
80
+ > ```
81
+ > /loop /wize-dev-story
82
+ > ```
83
+ >
84
+ > `/loop /wize-dev-story` drives one story at a time: TDD red-green-refactor, AC IDs in commits, `tea-design.md` contract, knowledge update on the 5 baseline axes, and a clean gate at the end. `/loop` keeps it going across the sprint's `ready-for-dev` queue until the user pauses.
85
+ >
86
+ > Next: `/wize-sprint-status` (Maria Hill) to acompanhar o progresso.
@@ -8,103 +8,50 @@ status: ready
8
8
 
9
9
  # Sprint Status
10
10
 
11
- **Goal.** Keep a snapshot of in-flight work that the team and stakeholders can read in 60 seconds. Sprint status is read by everyone, written by Hill (or delegated to Wizer); the source of truth is the file, not Slack.
12
-
13
- Update **daily** during a sprint, or after any state change (story moves, blocker appears, gate fails).
11
+ **Goal.** Read `.wize/implementation/sprint-status.yaml` in 60 seconds and say what to do next.
14
12
 
15
13
  ## Inputs
16
14
 
17
- - `.wize/solutioning/stories/` (current statuses)
18
- - `.wize/implementation/tea/{epic}/{story}/gate.md` (gate outcomes)
19
- - The team (verbal stand-up or async update)
15
+ - `.wize/implementation/sprint-status.yaml`
16
+ - `.wize/implementation/tea/{epic}/{story}/gate.md`
20
17
 
21
18
  ## Output
22
19
 
23
- - Updated entry in `.wize/implementation/sprint-status.md`.
20
+ - Updated sprint-status.yaml (if statuses changed).
21
+ - One recommended next workflow.
24
22
 
25
23
  ## Steps
26
24
 
27
- ### 1. Per story, name a status
28
-
29
- | Status | Meaning |
30
- |---|---|
31
- | `pulled` | Committed for this sprint, not started yet |
32
- | `in-progress` | Engineer actively working it (or paused < 1 day) |
33
- | `paused` | Started, paused > 1 day, reason listed |
34
- | `blocked` | Cannot proceed; depends on a named external resolution |
35
- | `in-review` | PR open; Hawkeye running design trace → review |
36
- | `gate-PASS` / `gate-CONCERNS` / `gate-FAIL` | TEA gate outcome |
37
- | `shipped` | Merged to main + deployed (when applicable) |
38
-
39
- ### 2. Blockers up front
40
-
41
- Blockers always appear in the top section. Each gets:
42
- - Owner (the person who can unblock it).
43
- - Specific ask (the action they should take).
44
- - Deadline.
45
-
46
- If a blocker sits longer than 2 days, Hill escalates. Stalled blockers are how sprints fail silently.
47
-
48
- ### 3. Trend
49
-
50
- Daily, write a one-line trend: *"On track."* / *"At risk for E03-S02 due to vendor outage."* / *"Slipping; will defer E04-S01 to next sprint."*
51
-
52
- ### 4. Capture decisions
53
-
54
- If something material was decided during the sprint that affects the plan (story sliced, scope dropped, ADR opened), append a one-line entry.
55
-
56
- ## File template
25
+ 1. **Load YAML.** Parse `development_status`.
26
+ 2. **Classify** each key: epic, story, retrospective.
27
+ 3. **Count** statuses.
28
+ 4. **Detect risks:**
29
+ - Story `in-progress` older than 2 days with no update.
30
+ - Blocked story without owner/deadline.
31
+ - Epic `in-progress` with no stories.
32
+ 5. **Recommend next step** (in priority order):
33
+ 1. Story `in-progress` → `/wize-dev-story`
34
+ 2. Story `review` `/wize-tea-review`
35
+ 3. Story `ready-for-dev` `/wize-dev-story`
36
+ 4. Story `backlog` → `/wize-create-story`
37
+ 5. Retrospective `optional` → `/wize-retrospective`
38
+ 6. All done → congratulate team.
39
+
40
+ ## Summary output
57
41
 
58
42
  ```markdown
59
- # Sprint status
43
+ ## Sprint Status
60
44
 
61
- ## Sprint 7 — 2026-06-12 → 2026-06-25
45
+ - Project: {project} ({project_key})
46
+ - Tracking: {tracking_system}
47
+ - Status file: .wize/implementation/sprint-status.yaml
62
48
 
63
- ### Day 4 (2026-06-15)
64
- **Trend:** On track.
49
+ **Stories:** backlog {n} | ready-for-dev {n} | in-progress {n} | review {n} | done {n}
50
+ **Epics:** backlog {n} | in-progress {n} | done {n}
65
51
 
66
- **Blockers:**
67
- - (none)
68
-
69
- **Stories:**
70
- - E01-S05 — gate-PASS — shipped (carry-over from S6).
71
- - E01-S06 — in-progress — Shuri.
72
- - E02-S02 — in-review — PR #418; Hawkeye doing trace.
73
- - E03-S02 — in-progress — Aaliyah.
74
- - E04-S01 — pulled — Shuri starts after E01-S06.
75
- - E02-S03 (stretch) — pulled — Aaliyah picks up if capacity allows.
76
-
77
- **Decisions:**
78
- - E03-S03 sliced into two stories (E03-S03a, E03-S03b) — ADR-009 incoming.
79
-
80
- ### Day 5 (2026-06-16)
81
- **Trend:** At risk on E02-S02 (vendor sandbox down; Hawkeye unblocked at 14:00).
82
- **Blockers:** Resolved.
83
- **Stories:** (changes from Day 4)
84
- - E02-S02 — gate-PASS at 16:30; merged.
85
- - E01-S06 — in-review — PR #419.
86
-
87
- ## Sprint 6 — 2026-05-29 → 2026-06-11
88
- {{archived}}
52
+ **Next:** /{next_workflow} ({next_story_id})
89
53
  ```
90
54
 
91
- ## Daily cadence (lean)
92
-
93
- A daily standup, when present, is 5 minutes max:
94
-
95
- - "What did I ship since last time?"
96
- - "What am I shipping next?"
97
- - "Anything blocking me?"
98
-
99
- Hill updates `sprint-status.md` immediately after; Wizer reads it before any other agent's session that day.
100
-
101
- ## Anti-patterns Hill rejects
102
-
103
- - "Status: in progress" for 4 days with no further detail. Either it really is, in which case slice progress, or it's stuck.
104
- - Blockers without an owner or a deadline.
105
- - Sprint goals that drift silently (added stories without removing others).
106
- - Stale entries in the file. Update daily or delegate the update.
107
-
108
55
  ## Hand-off
109
56
 
110
- > `sprint-status.md` updated. Day 5 trend: at-risk-mitigated. Wizer, if asked about state, the file is the answer. Pepper, brief stays valid; no scope move triggered.
57
+ > Status updated. Run the recommended workflow or call `/wize-sprint-planning` to reprioritize.
@@ -2,27 +2,89 @@
2
2
  code: wize-onboarding
3
3
  name: Onboarding
4
4
  owner: wize-orchestrator # Wizer
5
- status: stub
5
+ status: ready
6
6
  ---
7
7
 
8
8
  # Onboarding
9
9
 
10
- **Goal.** Post-install triage. Decide greenfield vs brownfield, profile(s), objective, then delegate.
10
+ **Goal.** First-contact triage after `npx wize-dev-kit install`. Decide greenfield vs brownfield, profile, objective, and route to the right persona. Always ask who the user is so the rest of the session feels personal.
11
+
12
+ Wizer drives. Each branch ends by handing off to a specific workflow with explicit handoff copy.
11
13
 
12
14
  ## Inputs
13
- - `.wize/config/project.toml` (just-created)
14
- - Repo state (git log, presence of `src/`, `package.json` etc.)
15
+
16
+ - `.wize/config/project.toml` (always present after install)
17
+ - `.wize/config/user.toml` (per-developer)
18
+ - `.wize/implementation/sprint-status.yaml` (when an active sprint exists)
19
+ - `.wize/planning/brief.md` (when Phase 1 started)
20
+ - `.wize/planning/prd.md` (when Phase 2 finished)
21
+ - Optional: chat message describing the user’s goal.
15
22
 
16
23
  ## Outputs
17
- - Updates to `.wize/config/project.toml`
18
- - A first task handed off to the right agent
24
+
25
+ - A **single handoff message** naming the next workflow to run, with the user’s name.
26
+ - Optional: `.wize/knowledge/onboarding-summary.md` written when state is ambiguous.
19
27
 
20
28
  ## Steps
21
- 1. **Greet.** "What are we working on?"
22
- 2. **Detect.** Brownfield? Offer `wize-document-project`.
23
- 3. **Profile check.** Confirm Core/+Web/+App and ask if changes needed.
24
- 4. **Objective.** One sentence: what does success look like in 30 days?
25
- 5. **Route.**
26
- - No brief? call Pepper (`wize-product-brief`).
27
- - Has brief? → call Maria Hill (`wize-create-prd`).
28
- - Mid-flight? resume where we left off (`sprint-status.md`).
29
+
30
+ ### 1. Greet the user
31
+
32
+ Read `name` from `.wize/config/user.toml`. Greet by name. Speak in `communication_language` from `project.toml`. If `name` is blank, ask once and persist it back to `user.toml` before continuing.
33
+
34
+ > "Welcome, {name}. You are at Wize onboarding for *{project_name}*."
35
+
36
+ ### 2. Detect project state
37
+
38
+ Inspect, in order:
39
+
40
+ | Path | Meaning |
41
+ |---|---|
42
+ | `.wize/implementation/sprint-status.yaml` | Active sprint exists. |
43
+ | `.wize/planning/prd.md` | Phase 2 (PRD) is done. |
44
+ | `.wize/planning/brief.md` | Phase 1 (Brief) is done. |
45
+ | `.wize/knowledge/document-project/*.md` | Brownfield baseline exists. |
46
+ | `package.json`, `src/`, etc. | Brownfield signals (vs. greenfield). |
47
+
48
+ If multiple artifacts exist, treat the **latest** (PRD > brief > baseline) as the current phase.
49
+
50
+ ### 3. State machine
51
+
52
+ - **S0 — No artifacts** → greenfield or no planning yet.
53
+ - **S1 — Baseline only** → brownfield, no brief.
54
+ - **S2 — Brief exists** → ready for PRD.
55
+ - **S3 — PRD exists** → ready for sprint planning.
56
+ - **S4 — Active sprint** → resume in-flight.
57
+
58
+ ### 4. Branch by state
59
+
60
+ | State | Action | Hand-off |
61
+ |---|---|---|
62
+ | S0 | Ask: "What are we building?" Confirm: brownfield or greenfield. Offer `/wize-document-project` (brownfield) or `/wize-product-brief` (greenfield). | "Run `/wize-document-project` (Tony + Peggy) to baseline the repo, or `/wize-product-brief` (Pepper) to write a brief." |
63
+ | S1 | Read `document-project/overview.md`; summarize the project in 3 bullets. Offer brief. | "Repo baselined. Run `/wize-product-brief` (Pepper)." |
64
+ | S2 | Read `brief.md` (3 bullets). Offer PRD. | "Brief ready. Run `/wize-create-prd` (Maria Hill)." |
65
+ | S3 | Read `prd.md` summary + `architecture.md` (if present). Offer sprint planning. | "PRD ready. Run `/wize-sprint-planning` (Maria Hill)." |
66
+ | S4 | Read `sprint-status.yaml` and surface: which stories are in progress, last gate. | "Sprint active. Run `/wize-sprint-status` (Maria Hill) for the full picture." |
67
+
68
+ ### 5. Confirm and exit
69
+
70
+ End every onboarding session with:
71
+
72
+ > "Onboarding complete. Next: `/wize-<next-workflow>` ({persona})."
73
+
74
+ Never auto-launch the next workflow. The user must confirm.
75
+
76
+ ## When to skip
77
+
78
+ - When the user already knows what they want and explicitly invokes another workflow, route directly. Do not force onboarding.
79
+ - When `WIZE_SKIP_ONBOARDING=1` is set, print a 1-line state summary and exit.
80
+
81
+ ## Anti-patterns Wizer rejects
82
+
83
+ - Launching the next workflow without confirmation.
84
+ - Re-asking the user’s name when it’s already in `user.toml`.
85
+ - Dumping the full project state to the user. Summarize in ≤ 5 bullets.
86
+ - Suggesting `wize-onboarding` from `/wize-help` once onboarding is complete. (Help should bypass onboarding for return users.)
87
+
88
+ ## Hand-off
89
+
90
+ > "Onboarding done. You are at **{state}**. Next: `/wize-<next-workflow>` ({persona})."
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ // allowlist.js — gate that filters arguments for an external pentest tool
4
+ // before they are passed to child_process.execFile. The list of allowed
5
+ // flags per tool lives in src/security-overlay/data/tool-allowlist.json.
6
+ //
7
+ // Schema (per tool, array of strings):
8
+ // "-foo" switch with no value
9
+ // "-foo:" switch that consumes the next argv as its value
10
+ // "-foo=bar" switch with a fixed value (only the literal "bar" is allowed)
11
+ // "--flag=" switch that consumes a value joined by '=' (e.g. --level=1)
12
+ //
13
+ // Invariant: args NOT in the allowlist (or args that look like values for
14
+ // flags not in the allowlist) are dropped. Positional args (targets, URLs)
15
+ // pass through unchanged.
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ class UnknownToolError extends Error {
21
+ constructor(tool) {
22
+ super(`Unknown tool "${tool}" — not in tool-allowlist.json. Refusing to invoke.`);
23
+ this.name = 'UnknownToolError';
24
+ this.tool = tool;
25
+ }
26
+ }
27
+
28
+ const DEFAULT_ALLOWLIST_PATH = path.join(__dirname, '..', 'data', 'tool-allowlist.json');
29
+
30
+ let _cache = null;
31
+ function _loadDefault() {
32
+ if (_cache) return _cache;
33
+ const raw = fs.readFileSync(DEFAULT_ALLOWLIST_PATH, 'utf8');
34
+ _cache = JSON.parse(raw);
35
+ return _cache;
36
+ }
37
+
38
+ function loadAllowlist(filePath) {
39
+ const fp = filePath || DEFAULT_ALLOWLIST_PATH;
40
+ return JSON.parse(fs.readFileSync(fp, 'utf8'));
41
+ }
42
+
43
+ // Heuristic for "is this arg a flag?": starts with '-' and is more than 1 char
44
+ // (a bare "-" is sometimes used as stdin, treat as positional).
45
+ function isFlag(arg) {
46
+ return typeof arg === 'string' && arg.length > 1 && arg[0] === '-';
47
+ }
48
+
49
+ // Classify one allowlist token into one of:
50
+ // { kind: 'switch' } — no value
51
+ // { kind: 'colon', prefix: '-foo' } — consumes next argv (e.g. -f path)
52
+ // { kind: 'equals', prefix: '--flag=' } — consumes value joined by '=' (e.g. --level=1)
53
+ // { kind: 'literal', full: '-foo=bar' } — only the exact form is allowed
54
+ //
55
+ // When matching an arg, we try in order: literal, then colon (exact prefix
56
+ // match, e.g. arg === '-u'), then equals (arg starts with prefix, e.g.
57
+ // arg === '--level=1'), then switch (exact equality).
58
+ function classify(token) {
59
+ if (token.endsWith(':')) {
60
+ return { kind: 'colon', prefix: token.slice(0, -1) };
61
+ }
62
+ if (token.endsWith('=') && token.startsWith('--')) {
63
+ return { kind: 'equals', prefix: token };
64
+ }
65
+ if (token.includes('=') && !token.endsWith('=')) {
66
+ return { kind: 'literal', full: token };
67
+ }
68
+ return { kind: 'switch' };
69
+ }
70
+
71
+ // Given a flag arg, find the first allowlist token that matches it.
72
+ function matchFlag(toolData, arg) {
73
+ // Literal first (most specific).
74
+ for (const tok of toolData) {
75
+ if (classify(tok).kind === 'literal' && arg === tok) return { consumeNext: false };
76
+ }
77
+ // Then colon (exact prefix match) — e.g. arg === '-u'.
78
+ for (const tok of toolData) {
79
+ if (classify(tok).kind === 'colon' && arg === classify(tok).prefix) return { consumeNext: true };
80
+ }
81
+ // Then equals — e.g. arg starts with '--level='.
82
+ for (const tok of toolData) {
83
+ if (classify(tok).kind === 'equals' && arg.startsWith(classify(tok).prefix)) return { consumeNext: false };
84
+ }
85
+ // Then switch.
86
+ for (const tok of toolData) {
87
+ if (classify(tok).kind === 'switch' && tok === arg) return { consumeNext: false };
88
+ }
89
+ return null;
90
+ }
91
+
92
+ // filterArgs(tool, args, allowlist) — keep only args allowed by the tool's
93
+ // allowlist, handling value-bearing flags correctly.
94
+ function filterArgs(tool, args, allowlist) {
95
+ const data = allowlist || _loadDefault();
96
+ if (!Object.prototype.hasOwnProperty.call(data, tool)) {
97
+ throw new UnknownToolError(tool);
98
+ }
99
+ const toolData = data[tool];
100
+
101
+ const out = [];
102
+ let i = 0;
103
+ const list = args || [];
104
+ while (i < list.length) {
105
+ const arg = list[i];
106
+ if (!isFlag(arg)) {
107
+ // Positional: target, URL, output path.
108
+ out.push(arg);
109
+ i++;
110
+ continue;
111
+ }
112
+ const m = matchFlag(toolData, arg);
113
+ if (!m) {
114
+ // Unknown flag — drop the flag. We do NOT also drop the next arg,
115
+ // because a positional after a stripped flag is still a positional
116
+ // (caller may have intended both to pass). However, common patterns
117
+ // are value-bearing: e.g. `--script vuln` — if the flag is dropped,
118
+ // "vuln" is also dropped to avoid leaking. This is the safe default.
119
+ if (looksLikeValueArg(list[i + 1])) {
120
+ // Consume the next arg as part of the dropped flag's expected value.
121
+ i += 2;
122
+ } else {
123
+ i++;
124
+ }
125
+ continue;
126
+ }
127
+ out.push(arg);
128
+ if (m.consumeNext) {
129
+ // Value-bearing: the next argv is the value. Pass it through.
130
+ if (i + 1 < list.length) {
131
+ out.push(list[i + 1]);
132
+ i += 2;
133
+ } else {
134
+ i++;
135
+ }
136
+ } else {
137
+ i++;
138
+ }
139
+ }
140
+ return out;
141
+ }
142
+
143
+ // A value arg is one that does NOT start with '-' (or is the bare "-").
144
+ function looksLikeValueArg(arg) {
145
+ return arg !== undefined && (typeof arg !== 'string' || arg.length === 0 || arg[0] !== '-');
146
+ }
147
+
148
+ module.exports = {
149
+ filterArgs,
150
+ loadAllowlist,
151
+ UnknownToolError,
152
+ classify,
153
+ matchFlag
154
+ };
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ // cli-runner.js — small helper so each phase script can be invoked
4
+ // either as a module (import + call) or as a CLI (with --securityDir /
5
+ // --scope / --active argv). The invoke-phase helper spawns the script
6
+ // as a Node subprocess; this module bridges that to the per-phase
7
+ // `runX` function exported by each script.
8
+ //
9
+ // Usage from a script:
10
+ // const { runX } = require('./run-x');
11
+ // module.exports = { runX };
12
+ // if (require.main === module) {
13
+ // require('../../../_shared/cli-runner.js').runFromArgv({
14
+ // fn: runX,
15
+ // argMap: { securityDir: 'securityDir', scopePath: 'scopePath', active: 'active' }
16
+ // });
17
+ // }
18
+ //
19
+ // argv shape: --securityDir=PATH --scope=PATH --active [script-specific]
20
+ // The function `fn` is called with the parsed object spread.
21
+
22
+ function parseArgv(argv) {
23
+ const out = {};
24
+ for (let i = 0; i < (argv || []).length; i++) {
25
+ const a = argv[i];
26
+ if (a === '--active') { out.active = true; continue; }
27
+ const eq = a.indexOf('=');
28
+ if (eq > 0) {
29
+ const key = a.slice(0, eq).replace(/^--/, '');
30
+ const val = a.slice(eq + 1);
31
+ out[camel(key)] = val;
32
+ continue;
33
+ }
34
+ if (a.startsWith('--') && i + 1 < argv.length && !argv[i + 1].startsWith('--')) {
35
+ out[camel(a.slice(2))] = argv[i + 1];
36
+ i++;
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+
42
+ function camel(s) {
43
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
44
+ }
45
+
46
+ async function runFromArgv({ fn, argMap = {} }) {
47
+ const argv = process.argv.slice(2);
48
+ const parsed = parseArgv(argv);
49
+ // Map --securityDir -> securityDir, --scope -> scopePath, etc.
50
+ const opts = {};
51
+ for (const [cliKey, optKey] of Object.entries(argMap)) {
52
+ if (cliKey in parsed) opts[optKey] = parsed[cliKey];
53
+ }
54
+ // Also accept any unprefixed camelCase keys directly.
55
+ for (const [k, v] of Object.entries(parsed)) {
56
+ if (!(k in opts)) opts[k] = v;
57
+ }
58
+ if (!('active' in opts)) opts.active = false;
59
+ // The phase script may also export its own CLI flags (--target for recon).
60
+ // We forward remaining --flags as a target-agnostic extraArgs-style list.
61
+ const extras = {};
62
+ for (const a of argv) {
63
+ if (a === '--active') continue;
64
+ const m = a.match(/^--([a-z0-9-]+)/);
65
+ if (m && !argMap[m[1]] && !argMap[camel(m[1])]) {
66
+ // Stash any flag the script might want to read itself.
67
+ extras[m[1]] = a.includes('=') ? a.slice(a.indexOf('=') + 1) : true;
68
+ }
69
+ }
70
+ Object.assign(opts, extras);
71
+ try {
72
+ const r = await fn(opts);
73
+ if (r && typeof r === 'object') {
74
+ // Print a one-line summary the orchestrator can show in its summary.
75
+ const summary = r.partialStatus
76
+ ? `${process.argv[1].split('/').pop().replace(/\.js$/, '')}: partial_status=${r.partialStatus} mode=${r.mode || 'passive'}`
77
+ : '';
78
+ if (summary) process.stdout.write(summary + '\n');
79
+ }
80
+ process.exit(0);
81
+ } catch (e) {
82
+ process.stderr.write(`✖ ${process.argv[1]}: ${e && e.message ? e.message : e}\n`);
83
+ process.exit(2);
84
+ }
85
+ }
86
+
87
+ module.exports = { parseArgv, runFromArgv };