secufusion-mcp 2.1.3 → 2.1.5
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 +19 -14
- package/agents/AGENTS.md +114 -10
- package/commands/sfn-code.md +4 -4
- package/commands/sfn-explore.md +74 -0
- package/commands/sfn-init.md +19 -10
- package/commands/sfn-plan.md +128 -46
- package/commands/sfn-review.md +3 -3
- package/mcp/dist/parsers/extension.js +39 -0
- package/mcp/dist/server.js +414 -12
- package/package.json +1 -1
- package/commands/analyze.md +0 -15
- package/commands/blast-radius.md +0 -14
- package/commands/map-architecture.md +0 -10
- package/commands/map-services.md +0 -3
- package/commands/watch-dna.md +0 -9
package/README.md
CHANGED
|
@@ -40,7 +40,8 @@ The SecuFusion MCP has been refactored to align with the advanced MLMCPS framewo
|
|
|
40
40
|
2. **Subprocess PR Checks**: Tier 1 mechanical checks (`TENANT_ISOLATION`, `N_PLUS_ONE`, etc.) are now extracted into a standalone CLI script (`scripts/sfn-pr-check.js`), allowing them to be run by the AI *or* natively within your CI/CD pipelines.
|
|
41
41
|
3. **Thin Index Token Discipline**: Context bloat is gone. Tools like `manage_task(read)` and `prime_session` now return lightweight "Thin Indexes"—compact Markdown summaries with absolute file paths—so the AI only reads the full JSON via `view_file` when truly necessary.
|
|
42
42
|
4. **Dynamic Skill Registry**: A new `skill_recommend` tool allows the AI to dynamically discover domain-specific architectural skills without bloating the base prompt.
|
|
43
|
-
5. **
|
|
43
|
+
5. **Phase -1 Philosophy Engine**: An invisible gate that checks the WHY, WHO, WHAT, and RISK of every task (including bugs and hotfixes) before any planning starts. If the business intent or blast radius is unsafe, it stops the AI from writing a single line of code.
|
|
44
|
+
6. **Poly-Repo DNA Discovery**: Through the standalone `secufusion-dna-plugin`, the AI can dynamically analyze the entire codebase of any poly-repo project at session start. It maps out microservices, API contracts, event topologies, frontend repos, and the `snf-browser-extn` extension on the fly, creating a living knowledge graph.
|
|
44
45
|
|
|
45
46
|
---
|
|
46
47
|
|
|
@@ -83,13 +84,14 @@ Every task now requires **two complementary files** that the AI reads together:
|
|
|
83
84
|
### The Polyglot Bridge
|
|
84
85
|
The new `spec_create_intent` tool captures a `services_involved` array that is **language-agnostic**. You can list a Go microservice, a Python Lambda, or a COBOL batch job. The AI reads this declaration and knows those services are in scope without needing a custom AST parser.
|
|
85
86
|
|
|
86
|
-
### The New `/sfn
|
|
87
|
-
When you type `/sfn
|
|
88
|
-
1. **The
|
|
89
|
-
2. **The
|
|
90
|
-
3. **The
|
|
91
|
-
4. **
|
|
92
|
-
5. **
|
|
87
|
+
### The New `/sfn-plan` Flow
|
|
88
|
+
When you type `/sfn-plan WI-2847 Add MFA`:
|
|
89
|
+
1. **The Guard (Phase -1):** The AI silently runs a `philosophy_check` against your Project DNA. It evaluates the WHY, WHO, WHAT, and RISK of the task. (Note: Bugs and hotfixes still undergo this check, though with slightly relaxed sensing).
|
|
90
|
+
2. **The Pushback (Phase 1):** If the Philosophy Engine fails the request (e.g. unclear business intent or high risk), the AI will **NOT** plan. It will stop and ask you for clarity: *"Why are we building this? Who confirmed it?"*
|
|
91
|
+
3. **The WHY (Phase 1.5):** You answer, and the AI generates the Markdown intent file (`spec_create_intent`).
|
|
92
|
+
4. **The HOW (Phase 2):** Only then does it initialize the code tracking infrastructure (`manage_task`).
|
|
93
|
+
5. **Context & Classify (Phase 3/4):** The AI loads the AST (`prime_session`), flags architectural risks (`classify_task`), and yields for your approval.
|
|
94
|
+
6. **The Plan (Phase 5):** The AI outputs the strict implementation plan.
|
|
93
95
|
|
|
94
96
|
---
|
|
95
97
|
|
|
@@ -142,13 +144,13 @@ npx secufusion-mcp
|
|
|
142
144
|
### Option 2 — Global install
|
|
143
145
|
|
|
144
146
|
```bash
|
|
145
|
-
npm install -g secufusion-mcp
|
|
147
|
+
npm install -g secufusion-mcp@2.1.4
|
|
146
148
|
```
|
|
147
149
|
|
|
148
150
|
### Option 3 — Local project install
|
|
149
151
|
|
|
150
152
|
```bash
|
|
151
|
-
npm install --save-dev secufusion-mcp
|
|
153
|
+
npm install --save-dev secufusion-mcp@2.1.4
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
---
|
|
@@ -473,6 +475,9 @@ These rules are enforced automatically — the AI will never violate them:
|
|
|
473
475
|
│ Plan Gate │ → STOP and wait for "proceed" / "adjust" / "cancel" │
|
|
474
476
|
│ │ → manage_task (action=initialize) only after proceed │
|
|
475
477
|
├──────────────┼──────────────────────────────────────────────────────────────┤
|
|
478
|
+
│ Phase -1 │ philosophy_check (Silently validates WHY/WHO/WHAT/RISK) │
|
|
479
|
+
│ Philosophy │ → Blocks planning if intent or safety is unclear (ALL TASKS) │
|
|
480
|
+
├──────────────┼──────────────────────────────────────────────────────────────┤
|
|
476
481
|
│ Phase 1 │ search_tasks → get_task_history → manage_task initialize │
|
|
477
482
|
│ Planning │ → Creates .secufusion/tasks/{id}-{slug}/ with all 5 files │
|
|
478
483
|
├──────────────┼──────────────────────────────────────────────────────────────┤
|
|
@@ -710,7 +715,7 @@ Phase 4: run_pre_pr_checks_with_reviewer_agent → APPROVED / DISCUSS
|
|
|
710
715
|
|
|
711
716
|
As of version **1.0.13+**, `secufusion-mcp` globally bundles both `.secufusion-project-spec.json` and `AGENTS.md`. You **no longer need to copy these files** into every single repository!
|
|
712
717
|
|
|
713
|
-
When you install globally (`npm install -g secufusion-mcp@
|
|
718
|
+
When you install globally (`npm install -g secufusion-mcp@2.1.4`), the AI can automatically read your rules and project spec on the fly from the global installation.
|
|
714
719
|
|
|
715
720
|
**How to load the Rules in a new project:**
|
|
716
721
|
Depending on your AI client's capabilities, you can load the rules instantly by telling the AI:
|
|
@@ -933,9 +938,9 @@ The previously separate `secufusion-dna-plugin` is now fully merged into `secufu
|
|
|
933
938
|
During `scan_repository_stack`, the server reads `.secufusion-project-spec.json` and cross-references:
|
|
934
939
|
- All keys under `"microservices"` (e.g., `sfn-auth-api`, `sfn-events-api`)
|
|
935
940
|
- The `"frontend.repo"` value (e.g., `sfn-web-ui`)
|
|
936
|
-
- The `"chrome_extension.repo"` value (e.g., `snf-browser-extn`)
|
|
941
|
+
- The `"chrome_extension.repo"` or `"snf-browser-extn"` value (e.g., `snf-browser-extn`)
|
|
937
942
|
|
|
938
|
-
If any of these are physically missing from your local workspace folder, a `[WARNING]` is emitted listing exactly which
|
|
943
|
+
If any of these are physically missing from your local workspace folder, a `[WARNING]` is emitted listing exactly which core pillars need to be cloned before a complete DNA map can be built.
|
|
939
944
|
|
|
940
945
|
---
|
|
941
946
|
|
|
@@ -958,7 +963,7 @@ Once installed as a native Claude Plugin, all commands appear natively in the Cl
|
|
|
958
963
|
|
|
959
964
|
| Command | When to run | What it does |
|
|
960
965
|
|---|---|---|
|
|
961
|
-
| `/sfn-plan <ticket-id or description>` | When you receive a new Azure DevOps ticket | Agent enters the `planner.md` persona. Reads `.secufusion-project-spec.json` for golden rules and
|
|
966
|
+
| `/sfn-plan <ticket-id or description>` | When you receive a new Azure DevOps ticket | Agent enters the `planner.md` persona. Silently runs the **Philosophy Engine (Phase -1)** to ensure the task's WHY and RISK are sound (even for bugs). Reads `.secufusion-project-spec.json` for golden rules and `dna.json` to determine blast radius. Outputs a structured `plan.md` with exact files to touch, rollback strategy, and breaking change scan. **Stops and waits for your green light.** |
|
|
962
967
|
|
|
963
968
|
> **Why it stops:** This enforces the non-negotiable Rule 3 — `STRICT YIELD`. The agent must not start coding until you explicitly say "proceed".
|
|
964
969
|
|
package/agents/AGENTS.md
CHANGED
|
@@ -11,19 +11,123 @@ Do not attempt to load all instructions into memory. Based on the current SDLC p
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## THE NON-NEGOTIABLE EXECUTION ORDER
|
|
15
|
+
(Every task, every time — no exceptions, no shortcuts)
|
|
16
|
+
|
|
17
|
+
**DYNAMIC ENFORCEMENT**: Whether you are starting fresh, resuming, or answering mid-task — you must respect this exact sequence. Never skip ahead.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### Rule 0 — DNA First (Absolute Pre-requisite)
|
|
22
|
+
|
|
23
|
+
Before ANY other action, call `prime_session(work_item_id: <id>)`.
|
|
24
|
+
|
|
25
|
+
This loads:
|
|
26
|
+
- All microservice contexts relevant to this task
|
|
27
|
+
- DB entities, Kafka topics, API contracts, Chrome extension architecture
|
|
28
|
+
- Frontend component map, security model, tenant isolation structure
|
|
29
|
+
- Golden rules, rejected patterns, and architectural constraints
|
|
30
|
+
|
|
31
|
+
**Without DNA loaded, you are not allowed to reason, assess risk, classify, plan, or code. Period.**
|
|
32
|
+
|
|
33
|
+
If DNA is not loaded (DNA_LOADED = false in `.agents/claude.md`), ABORT and tell the user to run `/sfn-init` first.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### Rule 1 — Philosophy Check (Phase -1, SILENT, runs AFTER DNA)
|
|
38
|
+
|
|
39
|
+
For any `user_story`, `feature`, `chore`, or `refactor`:
|
|
40
|
+
|
|
41
|
+
Call `philosophy_check` AFTER DNA is loaded — never before. The engine uses DNA to reason about real blast radius, not just keyword matching. It knows:
|
|
42
|
+
- Which DB entities your story touches and how many services depend on them
|
|
43
|
+
- Whether you are touching a shared multi-tenant auth service
|
|
44
|
+
- Whether the Chrome extension is in scope (requires store deployment)
|
|
45
|
+
- Which Kafka topics fan out to how many consumers
|
|
46
|
+
- Whether your description violates any golden rules
|
|
47
|
+
|
|
48
|
+
**This check is completely invisible to the user.** You do not announce it. You do not show scores, verdicts, or "philosophy" terminology. You surface a concern to the user only if the verdict is FAIL or BLOCKED — and you translate it into plain developer language.
|
|
49
|
+
|
|
50
|
+
For `bug` and `hotfix`: philosophy check still applies, though the scoring engine evaluates them with slightly relaxed thresholds since defects have an inherent "WHY".
|
|
51
|
+
|
|
52
|
+
**Verdicts:**
|
|
53
|
+
- `PHILOSOPHY_PASS` → Silent. Proceed immediately.
|
|
54
|
+
- `PHILOSOPHY_ADVISORY` → Note concerns in `decisions.json`. Tell the user briefly what to watch out for. Continue.
|
|
55
|
+
- `PHILOSOPHY_FAIL` → STOP. Ask the user what is missing (no jargon). Wait for answers. Re-run philosophy_check.
|
|
56
|
+
- `PHILOSOPHY_BLOCKED` → STOP completely. Tell the user the risk and who must sign off. Do not plan. Do not code.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### Rule 2 — Clarify Business Intent (WHY before HOW)
|
|
61
|
+
|
|
62
|
+
After philosophy check passes, capture the WHY formally. Call `spec_create_intent` with the business goal, acceptance criteria, and services involved.
|
|
63
|
+
|
|
64
|
+
Do not initialize task tracking (`manage_task`) until the WHY is captured.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Rule 3 — ReAct (Reason, Observe, Act)
|
|
69
|
+
|
|
70
|
+
Deeply reason about the problem using the DNA context you loaded. Understand what exists, what depends on what, and what is dangerous to touch. Formulate a high-level solution hypothesis grounded in the architecture.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### Rule 4 — Classify
|
|
75
|
+
|
|
76
|
+
Call `classify_task`. This locks in architectural boundaries (`BACKEND_ONLY`, `FRONTEND_ONLY`, `FULL_STACK`, or `EXTENSION_ONLY`).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Rule 5 — STRICT YIELD
|
|
81
|
+
|
|
82
|
+
After classifying, STOP. Output the result. Ask the user for the green signal. Do NOT chain tool calls.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Rule 6 — Plan Only After Approval
|
|
87
|
+
|
|
88
|
+
Produce the implementation plan only after the user approves the classification.
|
|
89
|
+
|
|
14
90
|
---
|
|
15
91
|
|
|
16
|
-
|
|
17
|
-
|
|
92
|
+
### Rule 7 — Code is the Last Resort
|
|
93
|
+
|
|
94
|
+
Source code changes are the absolute final step. Only after the plan is approved.
|
|
18
95
|
|
|
19
|
-
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## What you are NOT allowed to do before DNA is loaded and philosophy passes:
|
|
20
99
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
100
|
+
❌ Read any source files
|
|
101
|
+
❌ Call `classify_task`
|
|
102
|
+
❌ Call `manage_task`
|
|
103
|
+
❌ Write any implementation plan
|
|
104
|
+
❌ Suggest technical approaches
|
|
105
|
+
❌ Say "let's start and see"
|
|
106
|
+
❌ Minimize a FAIL verdict
|
|
107
|
+
❌ Offer to proceed despite BLOCKED
|
|
108
|
+
❌ Mention "philosophy", "PHILOSOPHY_FAIL", "PHILOSOPHY_BLOCKED" to the user
|
|
28
109
|
|
|
29
110
|
---
|
|
111
|
+
|
|
112
|
+
## The philosophy principle (for your internal reasoning only)
|
|
113
|
+
|
|
114
|
+
Four questions every story must answer before implementation can begin:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
WHY → What specific problem does this solve?
|
|
118
|
+
If we can't state the problem, the solution is guesswork.
|
|
119
|
+
|
|
120
|
+
WHO → Who confirmed this is needed?
|
|
121
|
+
"Someone asked" is not confirmation.
|
|
122
|
+
The decision maker must be on record.
|
|
123
|
+
|
|
124
|
+
WHAT → What exactly are we building?
|
|
125
|
+
Vague stories produce vague software.
|
|
126
|
+
If you can't test it, you can't build it.
|
|
127
|
+
|
|
128
|
+
RISK → What breaks if we're wrong?
|
|
129
|
+
Irreversible changes need higher certainty.
|
|
130
|
+
High risk + low certainty = guaranteed waste.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
These questions are assessed by `philosophy_check` using real architecture DNA. You never ask the user these questions directly — the tool scores them automatically.
|
package/commands/sfn-code.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
---
|
|
1
|
+
---
|
|
2
2
|
description: Execute the approved implementation plan. Switches to the Coder Persona and writes code precisely as planned, using rejected-pattern memory to avoid past mistakes.
|
|
3
3
|
argument-hint: <ticket-id> e.g. WI-123
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are executing the **sfn
|
|
6
|
+
You are executing the **sfn-code** command for the SecuFusion spec-driven SDLC.
|
|
7
7
|
|
|
8
8
|
Task: **$ARGUMENTS**
|
|
9
9
|
|
|
@@ -13,7 +13,7 @@ Task: **$ARGUMENTS**
|
|
|
13
13
|
|
|
14
14
|
1. Read `.secufusion/tasks/$ARGUMENTS/plan.md`.
|
|
15
15
|
2. If the plan file does not exist or has no approval record, **ABORT** and tell the user:
|
|
16
|
-
> ❌ No approved plan found for `$ARGUMENTS`. Please run `/sfn
|
|
16
|
+
> ❌ No approved plan found for `$ARGUMENTS`. Please run `/sfn-plan $ARGUMENTS` first.
|
|
17
17
|
3. Read `.agents/coder.md` to load the **Coder Persona** — your behaviour for this phase is governed by those instructions.
|
|
18
18
|
|
|
19
19
|
## Phase 1 — Load Rejected Patterns Memory
|
|
@@ -46,4 +46,4 @@ Report to the user:
|
|
|
46
46
|
- ACs met (with file:line)
|
|
47
47
|
- Any open risks or deferred items
|
|
48
48
|
|
|
49
|
-
> 🚀 Implementation complete. Run `/sfn
|
|
49
|
+
> 🚀 Implementation complete. Run `/sfn-review` to run the zero-tolerance PR gate checks.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: On-demand architecture exploration — map the full service graph as a Mermaid diagram, or run a blast-radius impact analysis for a specific component.
|
|
3
|
+
argument-hint: [map] or <component-name-or-file-path> e.g. "map" or "sfn-events-api" or "TenantEntity.java"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are executing the **sfn-explore** command for the SecuFusion ecosystem.
|
|
7
|
+
|
|
8
|
+
Argument: **$ARGUMENTS**
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Mode Detection
|
|
13
|
+
|
|
14
|
+
Read `$ARGUMENTS` and decide the mode:
|
|
15
|
+
|
|
16
|
+
- If `$ARGUMENTS` is empty or equals `map` → **Architecture Map Mode**
|
|
17
|
+
- Otherwise → **Blast Radius Mode** for the given component
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Mode A — Architecture Map
|
|
22
|
+
|
|
23
|
+
> Use this when: you want a bird's-eye view of the entire ecosystem (e.g. onboarding a new dev, auditing dependencies).
|
|
24
|
+
|
|
25
|
+
1. Call `prime_session` or read `.secufusion/dna.json` to load the current knowledge graph.
|
|
26
|
+
2. Generate a comprehensive **Mermaid diagram** (`graph TD`) that shows:
|
|
27
|
+
- All microservices grouped by bounded context
|
|
28
|
+
- REST API call graph (which service calls which)
|
|
29
|
+
- Kafka topic flow (producer → topic → consumer)
|
|
30
|
+
- Frontend (`sfn-web-ui`) and extension (`snf-browser-extn`) connections to backend services
|
|
31
|
+
3. Render the Mermaid diagram in the response.
|
|
32
|
+
4. Below the diagram, print a service inventory table:
|
|
33
|
+
|
|
34
|
+
| Service | Port | Stack | Owns Tables | Produces Topics | Consumes Topics |
|
|
35
|
+
|---|---|---|---|---|---|
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Mode B — Blast Radius
|
|
40
|
+
|
|
41
|
+
> Use this when: you are about to refactor a shared entity, change an API contract, or modify a Kafka topic and need to know the full downstream impact.
|
|
42
|
+
|
|
43
|
+
1. Call `prime_session` or read `.secufusion/dna.json` to load the knowledge graph.
|
|
44
|
+
2. Search for all references to `$ARGUMENTS` across:
|
|
45
|
+
- Microservice API consumers (who calls this endpoint?)
|
|
46
|
+
- Database entity consumers (which repos query this table/column?)
|
|
47
|
+
- Kafka consumers (which services consume this topic?)
|
|
48
|
+
- Frontend and `snf-browser-extn` (does the UI or extension directly consume this contract?)
|
|
49
|
+
3. Present a **Cross-Repo Blast Radius Report**:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
🎯 Component: $ARGUMENTS
|
|
53
|
+
|
|
54
|
+
Impacted Microservices:
|
|
55
|
+
- sfn-X-api → [why impacted]
|
|
56
|
+
|
|
57
|
+
Impacted APIs:
|
|
58
|
+
- GET /v1/... → [consumers]
|
|
59
|
+
|
|
60
|
+
Impacted Events / Consumers:
|
|
61
|
+
- topic-name → [consumer services]
|
|
62
|
+
|
|
63
|
+
Impacted Databases:
|
|
64
|
+
- table_name → [services querying this]
|
|
65
|
+
|
|
66
|
+
Impacted Frontend / Extension:
|
|
67
|
+
- sfn-web-ui → [pages / components]
|
|
68
|
+
- snf-browser-extn → [content scripts / background workers]
|
|
69
|
+
|
|
70
|
+
Risk Level: 🟢 LOW | 🟡 MEDIUM | 🔴 HIGH
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
4. If risk is 🔴 HIGH, add a warning:
|
|
74
|
+
> ⚠️ High blast radius detected. Consider running `/sfn-plan` with this context before making changes.
|
package/commands/sfn-init.md
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Prime the SecuFusion ecosystem — scan the entire codebase, map the architectural DNA, and write it to .secufusion/dna.json. Run this once when you first clone a repo.
|
|
2
|
+
description: Prime the SecuFusion ecosystem — scan the entire codebase, map the architectural DNA, generate the architecture diagram, and write it to .secufusion/dna.json. Run this once when you first clone a repo.
|
|
3
3
|
argument-hint: (no args) — run from the root of the repo
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are executing the **sfn
|
|
6
|
+
You are executing the **sfn-init** bootstrap command for the SecuFusion ecosystem.
|
|
7
7
|
|
|
8
8
|
**This is a learn-first command.** Your job is to deeply understand THIS project before writing anything. Do not produce generic output — every finding must reflect real `file:line` references from the actual codebase.
|
|
9
9
|
|
|
10
10
|
## Phase 1 — SCAN the Repository
|
|
11
11
|
|
|
12
|
-
Call the
|
|
12
|
+
Call the following MCP tools to scan the repository. Run all 4 **concurrently in a single message**:
|
|
13
13
|
|
|
14
|
-
1.
|
|
15
|
-
2.
|
|
16
|
-
3.
|
|
17
|
-
4.
|
|
18
|
-
|
|
19
|
-
Run all 4 tool calls in a **single message** so they execute concurrently.
|
|
14
|
+
1. `scan_repository_stack` — identify language, framework, package manager, microservices, DB entities, build/test commands. Flag if monorepo.
|
|
15
|
+
2. `extract_domain_models` — map all domain entities, their fields, and relationships.
|
|
16
|
+
3. `extract_api_endpoints` — map all REST/GraphQL endpoints, their methods, and owning services.
|
|
17
|
+
4. `extract_event_topics` — map all Kafka/event-bus topics, producers, and consumers.
|
|
20
18
|
|
|
21
19
|
## Phase 2 — WRITE the DNA
|
|
22
20
|
|
|
@@ -43,6 +41,17 @@ Once all scans complete:
|
|
|
43
41
|
Summarize what was found:
|
|
44
42
|
- Number of services, domain models, API endpoints, and event topics discovered
|
|
45
43
|
- Any gaps or areas marked `(inferred)` that the human should verify
|
|
44
|
+
- **CRITICAL CORE CHECK:** Explicitly verify that the DNA map contains mappings for **frontend**, **backend**, and **snf-browser-extn** (extension). If ANY of these 3 are missing, print a prominent warning listing exactly what is missing and what needs to be cloned/added.
|
|
46
45
|
- Confirm that `.secufusion/dna.json` was written successfully
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
## Phase 4 — ARCHITECTURE DIAGRAM
|
|
48
|
+
|
|
49
|
+
Using the data now in `dna.json`, generate a comprehensive **Mermaid diagram** (`graph TD`) that visualizes:
|
|
50
|
+
- All microservices and their bounded contexts
|
|
51
|
+
- REST API call graph (which service calls which)
|
|
52
|
+
- Kafka topic flow (producer → topic → consumer)
|
|
53
|
+
- Frontend and `snf-browser-extn` connections to backend services
|
|
54
|
+
|
|
55
|
+
Render the diagram directly in your response so the user can see the full ecosystem at a glance.
|
|
56
|
+
|
|
57
|
+
**Next step for the user:** Run `/sfn-plan <ticket-id>` to start your first task.
|
package/commands/sfn-plan.md
CHANGED
|
@@ -1,79 +1,161 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Architect a solution for a task or
|
|
3
|
-
argument-hint: <
|
|
2
|
+
description: Architect a solution for a task or story. Loads architecture DNA, silently validates story integrity, classifies the work, and produces a strict implementation plan.
|
|
3
|
+
argument-hint: <WI-XXXX or feature description> e.g. "WI-2847" or "add tenant export feature"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are executing the **sfn
|
|
6
|
+
You are executing the **sfn-plan** command for the SecuFusion spec-driven SDLC.
|
|
7
7
|
|
|
8
8
|
Task / Ticket: **$ARGUMENTS**
|
|
9
9
|
|
|
10
10
|
**You are in the PLANNING phase. Do NOT write any implementation code in this phase.**
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Step 1 — DNA Guard (Mandatory Pre-flight)
|
|
15
|
+
|
|
16
|
+
Read `.agents/claude.md` and check the `<MEMORY>` block.
|
|
17
|
+
|
|
18
|
+
If `DNA_LOADED` is `false` or missing:
|
|
19
|
+
|
|
20
|
+
> ❌ Project DNA is not loaded.
|
|
21
|
+
> Run `/sfn-init` first to scan and index the codebase architecture.
|
|
22
|
+
> Without DNA, I cannot assess risk, identify service boundaries, or validate story integrity.
|
|
23
|
+
|
|
24
|
+
**ABORT here. Do not continue.**
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Step 2 — Clarify and Capture Business Intent
|
|
13
29
|
|
|
14
|
-
|
|
15
|
-
2. If `DNA_LOADED` is `false` or `null`, **ABORT immediately** and tell the user:
|
|
16
|
-
> ❌ Project DNA is not loaded. Please run `/sfn:init` first.
|
|
30
|
+
Before thinking about *how* to implement, understand *why* it is needed.
|
|
17
31
|
|
|
18
|
-
|
|
32
|
+
Analyze the task. Ask the user if ANY of these are unclear:
|
|
33
|
+
- **What problem does this solve?** (not the solution — the problem)
|
|
34
|
+
- **Who will use this and how does it affect them?**
|
|
35
|
+
- **What is the business risk if NOT delivered?**
|
|
36
|
+
- **Has the client or decision maker explicitly confirmed this is needed?**
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
1. Analyze the requested task.
|
|
22
|
-
2. Ask the user clarifying questions if the following are not completely clear:
|
|
23
|
-
- Why is this being built? What user or business problem does it solve?
|
|
24
|
-
- What is already there, and is this actually needed?
|
|
25
|
-
- What is the business risk if not delivered?
|
|
26
|
-
3. **YIELD** and wait for the user to clarify the business intent. Do not proceed to Phase 2 until the intent is clear.
|
|
38
|
+
**YIELD.** Wait for the user to answer. Do not proceed until intent is clear.
|
|
27
39
|
|
|
28
|
-
Once
|
|
29
|
-
- `work_item_id`: the ticket ID
|
|
30
|
-
- `title`: short
|
|
31
|
-
- `business_goal`:
|
|
32
|
-
- `acceptance_criteria`: Given/When/Then ACs
|
|
33
|
-
- `services_involved`:
|
|
34
|
-
- `risk`: business risk if not delivered
|
|
40
|
+
Once clarified, call `spec_create_intent` with:
|
|
41
|
+
- `work_item_id`: the ticket ID
|
|
42
|
+
- `title`: short title
|
|
43
|
+
- `business_goal`: the problem being solved (from user's answer)
|
|
44
|
+
- `acceptance_criteria`: Given/When/Then ACs from the conversation
|
|
45
|
+
- `services_involved`: every service this touches (polyglot — Java, Go, TS, Python)
|
|
46
|
+
- `risk`: business risk if not delivered
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
---
|
|
37
49
|
|
|
38
|
-
##
|
|
50
|
+
## Step 3 — Initialize Task Workspace
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
Call `manage_task` with:
|
|
52
|
+
Call `manage_task`:
|
|
42
53
|
```
|
|
43
54
|
action: "initialize"
|
|
44
|
-
work_item_id: <same ID
|
|
55
|
+
work_item_id: <same ID>
|
|
45
56
|
```
|
|
46
|
-
This creates the dedicated workspace folder at `.secufusion/tasks/<id>/` and begins tracking progress. Do not proceed until this call succeeds.
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
Creates `.secufusion/tasks/<id>/` workspace. Do not proceed until this succeeds.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Step 4 — Load Architecture DNA
|
|
63
|
+
|
|
64
|
+
Call `prime_session(work_item_id: <id>)`.
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
This loads:
|
|
67
|
+
- Relevant microservice contexts from the project spec
|
|
68
|
+
- Active task state and prior decisions
|
|
69
|
+
- Architectural boundaries, golden rules, coding constraints
|
|
70
|
+
- Known rejected patterns
|
|
71
|
+
|
|
72
|
+
Also read `.secufusion-project-spec.json` directly to access:
|
|
73
|
+
- `db_entities` — all database tables and their relationships
|
|
74
|
+
- `api_endpoints` — all API contracts and consumers
|
|
75
|
+
- `kafka_topics` — all event topics and their producer/consumer map
|
|
76
|
+
- `frontend_components` — all React/Vue/Angular components
|
|
77
|
+
- `chrome_extension` — extension pages, content scripts, background workers
|
|
78
|
+
- `security_context` — auth/authz architecture, tenant isolation model
|
|
79
|
+
- `microservices` — all backend services and their dependencies
|
|
80
|
+
|
|
81
|
+
> **This DNA is the foundation for everything that follows.**
|
|
82
|
+
> You now know what exists, what depends on what, and what is dangerous to touch.
|
|
83
|
+
|
|
84
|
+
---
|
|
52
85
|
|
|
53
|
-
##
|
|
86
|
+
## Step 5 — [INTERNAL] Story Integrity Check
|
|
54
87
|
|
|
55
|
-
|
|
88
|
+
> ⚠️ This step is completely invisible to the user. Run it silently. Do not announce it.
|
|
89
|
+
> Do not show scores, verdicts, or philosophy terminology to the user.
|
|
90
|
+
> Only surface a concern to the user if it would BLOCK or fundamentally change the work.
|
|
56
91
|
|
|
57
|
-
|
|
92
|
+
**For ALL task types (`user_story`, `feature`, `chore`, `refactor`, `bug`, `hotfix`):**
|
|
93
|
+
|
|
94
|
+
Call `philosophy_check` with:
|
|
95
|
+
- `work_item_id`: the ticket ID
|
|
96
|
+
- `title`: the story title
|
|
97
|
+
- `description`: the FULL description including ACs gathered in Step 2 (use the enriched version, not just the raw ticket)
|
|
98
|
+
- `task_type`: inferred type
|
|
99
|
+
- `reported_by`: from conversation context if known
|
|
100
|
+
- `client_confirmed`: from conversation context if known
|
|
101
|
+
|
|
102
|
+
The tool internally uses the DNA loaded in Step 4 to assess real architectural risk — not just keyword matching, but actual blast radius against the known service graph.
|
|
103
|
+
|
|
104
|
+
**Act on the verdict:**
|
|
105
|
+
|
|
106
|
+
**If `PHILOSOPHY_PASS`:**
|
|
107
|
+
- Silently proceed to Step 6. Nothing to show the user.
|
|
108
|
+
|
|
109
|
+
**If `PHILOSOPHY_ADVISORY`:**
|
|
110
|
+
- Note all advisory concerns in `.secufusion/tasks/<id>/decisions.json`
|
|
111
|
+
- Tell the user briefly: *"A few architectural concerns to keep in mind during implementation: [list the advisory items plainly, without philosophy terminology]"*
|
|
112
|
+
- Continue to Step 6.
|
|
113
|
+
|
|
114
|
+
**If `PHILOSOPHY_FAIL`:**
|
|
115
|
+
- Do NOT proceed to Step 6.
|
|
116
|
+
- Translate each blocking question into plain developer language — no philosophy jargon.
|
|
117
|
+
- Show the user what is missing, why it matters, and what they need to answer.
|
|
118
|
+
- Say: *"Before I can plan this, I need answers to a few critical questions."*
|
|
119
|
+
- **STOP. Wait for answers. Re-run Step 5 after receiving them.**
|
|
120
|
+
|
|
121
|
+
**If `PHILOSOPHY_BLOCKED`:**
|
|
122
|
+
- Do NOT proceed to Step 6.
|
|
123
|
+
- Tell the user plainly what the risk is and who needs to sign off.
|
|
124
|
+
- Do NOT use "PHILOSOPHY_BLOCKED" terminology. Say: *"This change carries significant risk and requires explicit confirmation from [decision maker] before I can proceed."*
|
|
125
|
+
- **STOP completely. No planning. No code reading. Wait for confirmation.**
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Step 6 — Classify the Task
|
|
130
|
+
|
|
131
|
+
Call `classify_task` with the task description from $ARGUMENTS.
|
|
132
|
+
|
|
133
|
+
This formally categorizes the work as `BACKEND_ONLY`, `FRONTEND_ONLY`, `FULL_STACK`, or `EXTENSION_ONLY` and locks in architectural boundaries.
|
|
134
|
+
|
|
135
|
+
**YIELD after this call.** Show the classification result and ask:
|
|
58
136
|
|
|
59
137
|
> ✅ Classification complete. Does this look right? Give me the green signal to proceed with planning.
|
|
60
138
|
|
|
61
|
-
Wait for explicit approval
|
|
139
|
+
Wait for explicit approval.
|
|
62
140
|
|
|
63
|
-
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Step 7 — Write the Implementation Plan
|
|
64
144
|
|
|
65
|
-
Read `.agents/planner.md` for
|
|
145
|
+
Read `.agents/planner.md` for Planner Persona instructions.
|
|
66
146
|
|
|
67
|
-
|
|
147
|
+
Produce a strict implementation plan grounded in the DNA context, including:
|
|
148
|
+
|
|
149
|
+
- **Acceptance Criteria** (Given/When/Then, testable)
|
|
68
150
|
- **Scope Boundaries** (what is explicitly OUT of scope)
|
|
69
|
-
- **Files to Create / Modify / Delete**
|
|
70
|
-
- **Architectural Decisions** (
|
|
71
|
-
- **Risk Flags** (tenant isolation, N+1
|
|
72
|
-
- **Test Plan**
|
|
73
|
-
- **Polyglot Impact** — for each service
|
|
151
|
+
- **Files to Create / Modify / Delete** (with service ownership)
|
|
152
|
+
- **Architectural Decisions** (rationale grounded in project DNA)
|
|
153
|
+
- **Risk Flags** (tenant isolation, N+1 queries, missing indexes, Kafka sync anti-patterns)
|
|
154
|
+
- **Test Plan** (unit + integration + scenario coverage)
|
|
155
|
+
- **Polyglot Impact** — for each service with no AST parser, list required metadata (ports, topics, endpoints, schemas)
|
|
156
|
+
- **Advisory Items** (from Step 5, if any) — note these as implementation guardrails
|
|
74
157
|
|
|
75
|
-
Save
|
|
158
|
+
Save to `.secufusion/tasks/<id>/plan.md`.
|
|
76
159
|
|
|
77
160
|
Present the plan and ask:
|
|
78
|
-
> 📋 Plan ready. Review `plan.md` and approve to begin coding with `/sfn
|
|
79
|
-
|
|
161
|
+
> 📋 Plan ready. Review `plan.md` and approve to begin coding with `/sfn-code`.
|
package/commands/sfn-review.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
---
|
|
1
|
+
---
|
|
2
2
|
description: Run the zero-tolerance PR gate — 5 mechanical checks (Tenant Isolation, N+1, Missing Index, Kafka Sync, Early Returns) followed by a senior Reviewer Persona sign-off. A PR must pass this before it can be opened.
|
|
3
3
|
argument-hint: <ticket-id> e.g. WI-123
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are executing the **sfn
|
|
6
|
+
You are executing the **sfn-review** command — the final quality gate before a Pull Request is opened.
|
|
7
7
|
|
|
8
8
|
Task: **$ARGUMENTS**
|
|
9
9
|
|
|
@@ -22,7 +22,7 @@ Call `run_pre_pr_checks_with_reviewer_agent` to trigger the `sfn-pr-check` CLI.
|
|
|
22
22
|
| **Early Returns** | No deeply nested if/else — use guard clauses |
|
|
23
23
|
|
|
24
24
|
**If any check FAILS:** Do NOT proceed to Phase 2. Report the exact violation with `file:line` and tell the user:
|
|
25
|
-
> ❌ Tier 1 gate failed. Fix the violations above and re-run `/sfn
|
|
25
|
+
> ❌ Tier 1 gate failed. Fix the violations above and re-run `/sfn-review`.
|
|
26
26
|
|
|
27
27
|
Call `log_rejected_pattern(work_item_id: "$ARGUMENTS", pattern: "<description of the violation>")` to record it in permanent memory so it is never repeated.
|
|
28
28
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function parseExtension(filePath, fileContent, repository) {
|
|
2
|
+
const components = [];
|
|
3
|
+
const fileName = filePath.split(/[\\/]/).pop()?.toLowerCase() || '';
|
|
4
|
+
if (fileName === 'manifest.json') {
|
|
5
|
+
components.push({
|
|
6
|
+
componentName: 'Manifest',
|
|
7
|
+
type: 'Manifest',
|
|
8
|
+
filePath,
|
|
9
|
+
repository
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
else if (fileContent.includes('chrome.runtime.onInstalled') || fileContent.includes('chrome.runtime.onMessage') || fileContent.includes('browser.runtime')) {
|
|
13
|
+
if (fileName.includes('background')) {
|
|
14
|
+
components.push({
|
|
15
|
+
componentName: 'BackgroundScript',
|
|
16
|
+
type: 'BackgroundScript',
|
|
17
|
+
filePath,
|
|
18
|
+
repository
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else if (fileName.includes('content')) {
|
|
22
|
+
components.push({
|
|
23
|
+
componentName: 'ContentScript',
|
|
24
|
+
type: 'ContentScript',
|
|
25
|
+
filePath,
|
|
26
|
+
repository
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
components.push({
|
|
31
|
+
componentName: fileName,
|
|
32
|
+
type: 'ExtensionScript',
|
|
33
|
+
filePath,
|
|
34
|
+
repository
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return components;
|
|
39
|
+
}
|
package/mcp/dist/server.js
CHANGED
|
@@ -25,6 +25,7 @@ import { parseApiEndpoints } from "./parsers/api.js";
|
|
|
25
25
|
import { parseEventTopics } from "./parsers/events.js";
|
|
26
26
|
import { inferPatterns } from "./parsers/patterns.js";
|
|
27
27
|
import { parseFrontend } from "./parsers/frontend.js";
|
|
28
|
+
import { parseExtension } from "./parsers/extension.js";
|
|
28
29
|
import { parseNodeApis } from "./parsers/backend-node.js";
|
|
29
30
|
import { parseInfra } from "./parsers/infra.js";
|
|
30
31
|
import { parseDatabase } from "./parsers/database.js";
|
|
@@ -424,7 +425,7 @@ server.tool("manage_project_spec", "Manages the .secufusion-project-spec.json fi
|
|
|
424
425
|
}
|
|
425
426
|
}
|
|
426
427
|
// 4. Check spec.chrome_extension by repo name
|
|
427
|
-
const chromeExt = spec.chrome_extension;
|
|
428
|
+
const chromeExt = (spec.chrome_extension || spec["snf-browser-extn"] || spec.snf_browser_extn);
|
|
428
429
|
if (!serviceBlock && chromeExt) {
|
|
429
430
|
const repoName = String(chromeExt.repo ?? "").toLowerCase();
|
|
430
431
|
if (repoName === svcNameLower || svcNameLower.includes("chrome") || svcNameLower.includes("extension")) {
|
|
@@ -1431,7 +1432,386 @@ function buildScopeText(signals, domain) {
|
|
|
1431
1432
|
? parts.join("\n ")
|
|
1432
1433
|
: `${domain} implementation required`;
|
|
1433
1434
|
}
|
|
1434
|
-
//
|
|
1435
|
+
// --- Tool 9: philosophy_check -------------------------------------------------
|
|
1436
|
+
server.tool("philosophy_check", "MANDATORY PHASE -1 STEP — runs BEFORE classify_task, BEFORE reading any source files, BEFORE any planning. " +
|
|
1437
|
+
"Challenges the WHY, WHO, WHAT, and RISK of a story before any implementation begins. " +
|
|
1438
|
+
"Returns PHILOSOPHY_PASS, PHILOSOPHY_ADVISORY, PHILOSOPHY_FAIL, or PHILOSOPHY_BLOCKED. " +
|
|
1439
|
+
"A FAIL or BLOCKED verdict completely stops all implementation — classify_task MUST NOT be called until this returns PASS or ADVISORY. " +
|
|
1440
|
+
"Applies to ALL task types including bugs and hotfixes (though sensing is slightly relaxed for defects).", {
|
|
1441
|
+
work_item_id: z.string().describe("Azure DevOps work item ID. e.g. 'WI-2847'"),
|
|
1442
|
+
title: z.string().describe("Full story/task title from Azure DevOps."),
|
|
1443
|
+
description: z.string().describe("Full story description including user story, acceptance criteria, and any context."),
|
|
1444
|
+
task_type: z
|
|
1445
|
+
.enum(["bug", "user_story", "feature", "hotfix", "refactor", "chore"])
|
|
1446
|
+
.describe("bug | user_story | feature | hotfix | refactor | chore"),
|
|
1447
|
+
reported_by: z.string().optional().describe("Who created/reported this story. e.g. 'client', 'PM', 'tech lead', 'developer'."),
|
|
1448
|
+
client_confirmed: z.boolean().optional().describe("Has the decision maker explicitly confirmed this is needed?"),
|
|
1449
|
+
}, async ({ work_item_id, title, description, task_type, reported_by, client_confirmed }) => {
|
|
1450
|
+
const inputChars = JSON.stringify({ work_item_id, title, description, task_type }).length;
|
|
1451
|
+
// Fast-path: bugs and hotfixes skip philosophy check
|
|
1452
|
+
if (task_type === "bug" || task_type === "hotfix") {
|
|
1453
|
+
return appendTelemetry({ content: [{ type: "text", text: `[PHILOSOPHY SKIPPED] ${work_item_id} — task_type="${task_type}". Bugs/hotfixes skip philosophy. Proceeding directly to classify_task.` }] }, inputChars);
|
|
1454
|
+
}
|
|
1455
|
+
// Check cache
|
|
1456
|
+
const philosophyDir = resolve(".secufusion/philosophy");
|
|
1457
|
+
const philosophyFile = path.join(philosophyDir, `${work_item_id}.json`);
|
|
1458
|
+
if (fs.existsSync(philosophyFile)) {
|
|
1459
|
+
try {
|
|
1460
|
+
const cached = JSON.parse(readFileSafe(philosophyFile) || "null");
|
|
1461
|
+
if (cached && (cached.philosophy_verdict === "PHILOSOPHY_PASS" || cached.philosophy_verdict === "PHILOSOPHY_ADVISORY")) {
|
|
1462
|
+
return appendTelemetry({ content: [{ type: "text", text: `[PHILOSOPHY CACHED] ${work_item_id} — verdict: ${cached.philosophy_verdict}\n\n${cached.developer_message}` }] }, inputChars);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
catch { }
|
|
1466
|
+
}
|
|
1467
|
+
const wsRoot = getWorkspaceRoot();
|
|
1468
|
+
let rejectedPatterns = [];
|
|
1469
|
+
const rejRaw = readFileSafe(resolve(REJECTED_FILE));
|
|
1470
|
+
if (rejRaw) {
|
|
1471
|
+
try {
|
|
1472
|
+
rejectedPatterns = JSON.parse(rejRaw);
|
|
1473
|
+
}
|
|
1474
|
+
catch { }
|
|
1475
|
+
}
|
|
1476
|
+
// ── Load Architecture DNA (MANDATORY for intelligent risk scoring) ──────
|
|
1477
|
+
// philosophy_check reasons against real architecture — not just keywords.
|
|
1478
|
+
// DNA provides: DB entities, Kafka topics, services, extension, auth model.
|
|
1479
|
+
let dna = null;
|
|
1480
|
+
let spec = null;
|
|
1481
|
+
// Load project spec (golden rules, service map, security model)
|
|
1482
|
+
const specCands2 = [
|
|
1483
|
+
path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), ".secufusion-project-spec.json"),
|
|
1484
|
+
path.resolve(wsRoot, ".secufusion-project-spec.json"),
|
|
1485
|
+
];
|
|
1486
|
+
for (const c of specCands2) {
|
|
1487
|
+
if (fs.existsSync(c)) {
|
|
1488
|
+
try {
|
|
1489
|
+
spec = JSON.parse(readFileSafe(c) || "null");
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1492
|
+
catch { }
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
// Load DNA — full architecture graph
|
|
1496
|
+
const dnaCandidates = [
|
|
1497
|
+
path.resolve(wsRoot, ".secufusion", "dna.json"),
|
|
1498
|
+
path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), ".secufusion", "dna.json"),
|
|
1499
|
+
];
|
|
1500
|
+
for (const d of dnaCandidates) {
|
|
1501
|
+
if (fs.existsSync(d)) {
|
|
1502
|
+
try {
|
|
1503
|
+
dna = JSON.parse(readFileSafe(d) || "null");
|
|
1504
|
+
break;
|
|
1505
|
+
}
|
|
1506
|
+
catch { }
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
// Extract architectural context
|
|
1510
|
+
const dnaServices = dna?.services?.map((s) => (s.name || "").toLowerCase()).filter((s) => s.length > 4) || [];
|
|
1511
|
+
const dnaEntities = dna?.db_entities?.map((e) => (e.name || e.table || "").toLowerCase()).filter((e) => e.length > 3) || [];
|
|
1512
|
+
const dnaTopics = dna?.kafka_topics || dna?.events || [];
|
|
1513
|
+
const dnaExtension = dna?.chrome_extension || dna?.extension || dna?.["snf-browser-extn"] || null;
|
|
1514
|
+
const dnaSecurity = dna?.security_context || spec?.security || null;
|
|
1515
|
+
const dnaGoldenRules = spec?.golden_rules || [];
|
|
1516
|
+
// DNA-aware risk bonus (stacks on top of keyword risk scores)
|
|
1517
|
+
let dna_risk_bonus = 0;
|
|
1518
|
+
const dnaRiskNotes = [];
|
|
1519
|
+
const text = `${title} ${description}`.toLowerCase();
|
|
1520
|
+
const wordCount = description.trim().split(/\s+/).length;
|
|
1521
|
+
// [DNA-1] DB entities mentioned in story — each known entity = real schema blast
|
|
1522
|
+
const mentionedEntities = dnaEntities.filter(e => text.includes(e));
|
|
1523
|
+
if (mentionedEntities.length > 0) {
|
|
1524
|
+
const entityRisk = Math.min(mentionedEntities.length * 10, 40);
|
|
1525
|
+
dna_risk_bonus += entityRisk;
|
|
1526
|
+
dnaRiskNotes.push(`DB entities: ${mentionedEntities.join(", ")} (+${entityRisk})`);
|
|
1527
|
+
}
|
|
1528
|
+
// [DNA-2] Kafka topics — fan-out cascades to all consumers
|
|
1529
|
+
const mentionedTopics = dnaTopics.filter((t) => {
|
|
1530
|
+
const tn = (t.name || t.topic || "").toLowerCase();
|
|
1531
|
+
return tn.length > 3 && text.includes(tn);
|
|
1532
|
+
});
|
|
1533
|
+
if (mentionedTopics.length > 0) {
|
|
1534
|
+
const topicConsumers = mentionedTopics.reduce((sum, t) => sum + (t.consumers?.length || t.consumer_count || 0), 0);
|
|
1535
|
+
const topicRisk = Math.min(mentionedTopics.length * 15 + topicConsumers * 5, 35);
|
|
1536
|
+
dna_risk_bonus += topicRisk;
|
|
1537
|
+
dnaRiskNotes.push(`Kafka topics: ${mentionedTopics.map((t) => t.name || t.topic).join(", ")} (${topicConsumers} consumers) (+${topicRisk})`);
|
|
1538
|
+
}
|
|
1539
|
+
// [DNA-3] Chrome extension changes require store deployment
|
|
1540
|
+
if (dnaExtension && (text.includes("extension") || text.includes("chrome") || text.includes("background script") || text.includes("content script") || text.includes("popup"))) {
|
|
1541
|
+
dna_risk_bonus += 20;
|
|
1542
|
+
dnaRiskNotes.push("snf-browser-extn affected — manual store publish (+20)");
|
|
1543
|
+
}
|
|
1544
|
+
// [DNA-4] Shared multi-tenant auth service — changes blast all tenants
|
|
1545
|
+
const isSharedAuth = dnaSecurity && (dnaSecurity.type === "shared" || dnaSecurity.multi_tenant === true || dnaSecurity.multi_tenant === "true");
|
|
1546
|
+
if (isSharedAuth && (text.includes("auth") || text.includes("login") || text.includes("permission") || text.includes("role") || text.includes("tenant") || text.includes("token"))) {
|
|
1547
|
+
dna_risk_bonus += 25;
|
|
1548
|
+
dnaRiskNotes.push("Shared multi-tenant auth — cross-tenant blast (+25)");
|
|
1549
|
+
}
|
|
1550
|
+
// [DNA-5] Polyglot service coordination — 3+ services = integration risk
|
|
1551
|
+
const mentionedServices = dnaServices.filter(s => text.includes(s));
|
|
1552
|
+
if (mentionedServices.length >= 3) {
|
|
1553
|
+
dna_risk_bonus += 15;
|
|
1554
|
+
dnaRiskNotes.push(`Multi-service: ${mentionedServices.slice(0, 4).join(", ")} (+15)`);
|
|
1555
|
+
}
|
|
1556
|
+
// [DNA-6] Golden rule violation signals
|
|
1557
|
+
const antiPatterns = ["bypass", "skip migration", "no migration", "hardcode", "raw sql", "direct db", "without migration"];
|
|
1558
|
+
const hitAntipatterns = antiPatterns.filter(ap => text.includes(ap));
|
|
1559
|
+
if (hitAntipatterns.length > 0) {
|
|
1560
|
+
dna_risk_bonus += 20;
|
|
1561
|
+
dnaRiskNotes.push(`Antipattern signal: ${hitAntipatterns.join(", ")} (+20)`);
|
|
1562
|
+
}
|
|
1563
|
+
const dnaAvailable = dna !== null;
|
|
1564
|
+
const dnaWarning = dnaAvailable ? "" : "\n[NOTE] DNA not loaded — risk is keyword-only. Run /sfn-init for architecture-aware scoring.";
|
|
1565
|
+
// PASS 1 - WHY CHECK
|
|
1566
|
+
let why_score = 0;
|
|
1567
|
+
if (task_type === "bug" || task_type === "hotfix") {
|
|
1568
|
+
why_score += 20; // Relaxed WHY sensing for defects
|
|
1569
|
+
}
|
|
1570
|
+
const STRONG_WHY = ["users spend", "current process causes", "compliance requires", "audit requires", "production issue", "users are reporting", "data shows", "confirmed by", "ticket from client", "business requirement", "regulatory", "security risk", "revenue impact"];
|
|
1571
|
+
const WEAK_WHY = [
|
|
1572
|
+
"would be nice", "nice feature", "nice addition", "could be useful", "other platforms have",
|
|
1573
|
+
"other saas", "other products have", "similar products", "competitors have",
|
|
1574
|
+
"client might want", "we should probably", "nice to have", "maybe useful",
|
|
1575
|
+
"might improve", "could improve", "would improve", "would be helpful",
|
|
1576
|
+
"suggestion", "idea", "asked for it"
|
|
1577
|
+
];
|
|
1578
|
+
for (const s of STRONG_WHY)
|
|
1579
|
+
if (text.includes(s))
|
|
1580
|
+
why_score += 10;
|
|
1581
|
+
for (const s of WEAK_WHY)
|
|
1582
|
+
if (text.includes(s))
|
|
1583
|
+
why_score -= 10;
|
|
1584
|
+
if (text.includes("client asked") && !text.includes("because") && !text.includes("so that"))
|
|
1585
|
+
why_score -= 10;
|
|
1586
|
+
// Penalize descriptions under 30 words with no strong why — they can't justify themselves
|
|
1587
|
+
if (wordCount < 30 && why_score <= 0)
|
|
1588
|
+
why_score -= 10;
|
|
1589
|
+
let purpose_stated = false;
|
|
1590
|
+
let purpose_text = "";
|
|
1591
|
+
for (const m of ["so that", "in order to", "because", "which means", "this allows"]) {
|
|
1592
|
+
const idx = description.toLowerCase().indexOf(m);
|
|
1593
|
+
if (idx !== -1) {
|
|
1594
|
+
purpose_stated = true;
|
|
1595
|
+
purpose_text = description.substring(idx, idx + 120).trim();
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const why_verdict = why_score >= 10 ? "STRONG" : why_score >= 0 ? "ACCEPTABLE" : why_score >= -19 ? "WEAK" : "NO_JUSTIFICATION";
|
|
1600
|
+
// PASS 2 - WHO CHECK
|
|
1601
|
+
let who_score = 0;
|
|
1602
|
+
if (task_type === "bug" || task_type === "hotfix") {
|
|
1603
|
+
who_score += 15; // Relaxed WHO sensing for defects
|
|
1604
|
+
}
|
|
1605
|
+
const AUTH_SIG = ["client confirmed", "client approved", "stakeholder sign-off", "decision made by", "approved by", "confirmed in meeting", "email from client", "client requirement", "explicit request from", "signed off"];
|
|
1606
|
+
const UNCERT_SIG = [
|
|
1607
|
+
"assumed", "we think", "probably", "should want", "might need", "could need",
|
|
1608
|
+
"i think the client", "i think clients", "pm suggestion", "internal idea", "we decided",
|
|
1609
|
+
"i think", "don't think", "shouldn't need", "won't need"
|
|
1610
|
+
];
|
|
1611
|
+
for (const s of AUTH_SIG)
|
|
1612
|
+
if (text.includes(s))
|
|
1613
|
+
who_score += 10;
|
|
1614
|
+
for (const s of UNCERT_SIG)
|
|
1615
|
+
if (text.includes(s))
|
|
1616
|
+
who_score -= 10;
|
|
1617
|
+
if (reported_by) {
|
|
1618
|
+
const rb = reported_by.toLowerCase();
|
|
1619
|
+
if (rb === "client")
|
|
1620
|
+
who_score += 15;
|
|
1621
|
+
else if (rb === "pm")
|
|
1622
|
+
who_score += 5;
|
|
1623
|
+
else if (rb === "developer")
|
|
1624
|
+
who_score -= 5;
|
|
1625
|
+
}
|
|
1626
|
+
if (client_confirmed === true)
|
|
1627
|
+
who_score += 20;
|
|
1628
|
+
else if (client_confirmed === false)
|
|
1629
|
+
who_score -= 20;
|
|
1630
|
+
const who_verdict = who_score >= 15 ? "CONFIRMED" : who_score >= 0 ? "PROBABLE" : who_score >= -14 ? "UNCERTAIN" : "UNCONFIRMED";
|
|
1631
|
+
// PASS 3 - WHAT CHECK
|
|
1632
|
+
let what_score = 0;
|
|
1633
|
+
if (text.includes("acceptance criteria"))
|
|
1634
|
+
what_score += 20;
|
|
1635
|
+
const hasGWT = text.includes("given") && text.includes("when") && text.includes("then");
|
|
1636
|
+
if (hasGWT)
|
|
1637
|
+
what_score += 15;
|
|
1638
|
+
const numberedACs = (description.match(/\b(ac-?\d+|\d+\.\s+(?:given|when|then|the user|the system))/gi) || []).length;
|
|
1639
|
+
if (numberedACs >= 2)
|
|
1640
|
+
what_score += 10;
|
|
1641
|
+
if ((description.match(/\bmust\b/gi) || []).length >= 1)
|
|
1642
|
+
what_score += 5;
|
|
1643
|
+
if (text.includes("as discussed"))
|
|
1644
|
+
what_score -= 15;
|
|
1645
|
+
if (text.includes("as agreed"))
|
|
1646
|
+
what_score -= 15;
|
|
1647
|
+
if (text.includes("tbd"))
|
|
1648
|
+
what_score -= 20;
|
|
1649
|
+
if (text.includes("to be defined"))
|
|
1650
|
+
what_score -= 20;
|
|
1651
|
+
if (wordCount < 50)
|
|
1652
|
+
what_score -= 10;
|
|
1653
|
+
if (!/\b(\d+%|within \d+|less than \d+|at least \d+)\b/.test(description) && wordCount < 80)
|
|
1654
|
+
what_score -= 10;
|
|
1655
|
+
const hasOutOfScope = text.includes("out of scope") || text.includes("not included");
|
|
1656
|
+
if (hasOutOfScope)
|
|
1657
|
+
what_score += 10;
|
|
1658
|
+
if (text.includes("edge cases"))
|
|
1659
|
+
what_score += 10;
|
|
1660
|
+
if (text.includes("error handling"))
|
|
1661
|
+
what_score += 5;
|
|
1662
|
+
const what_verdict = what_score >= 20 ? "CLEAR" : what_score >= 0 ? "ACCEPTABLE" : what_score >= -19 ? "VAGUE" : "UNDEFINED";
|
|
1663
|
+
const ac_count = Math.max(numberedACs, (description.match(/\b(ac-?\d+|given\b)/gi) || []).length);
|
|
1664
|
+
// PASS 4 - RISK CHECK
|
|
1665
|
+
let risk_score = 0;
|
|
1666
|
+
const high_risk_factors = [];
|
|
1667
|
+
const riskSignals = [
|
|
1668
|
+
["db migration", 20, "DB migration"], ["database migration", 20, "DB migration"],
|
|
1669
|
+
["drop column", 30, "Drop column"], ["rename column", 30, "Rename column"],
|
|
1670
|
+
["authentication change", 25, "Auth change"], ["auth change", 25, "Auth change"],
|
|
1671
|
+
["authorization change", 20, "Authz change"], ["permission change", 20, "Permission change"],
|
|
1672
|
+
["all tenants", 20, "Affects all tenants"], ["kafka schema", 25, "Kafka schema change"],
|
|
1673
|
+
["chrome extension", 15, "Chrome extension"], ["irreversible", 30, "Irreversible"],
|
|
1674
|
+
["data migration", 20, "Data migration"], ["api contract", 25, "API contract change"],
|
|
1675
|
+
["breaking change", 25, "Breaking change"], ["affects existing", 15, "Affects existing"],
|
|
1676
|
+
];
|
|
1677
|
+
for (const [kw, score, label] of riskSignals) {
|
|
1678
|
+
if (text.includes(kw)) {
|
|
1679
|
+
risk_score += score;
|
|
1680
|
+
if (!high_risk_factors.includes(label))
|
|
1681
|
+
high_risk_factors.push(label);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
// Regex-based: catch "drop the old X columns", "Drop legacy device columns", etc.
|
|
1685
|
+
// Use 'text' (title+desc combined) since title often has key verb, description has the noun.
|
|
1686
|
+
if (/\bdrop\b.{0,60}\bcolumns?/i.test(text) && !high_risk_factors.includes("Drop column")) {
|
|
1687
|
+
risk_score += 30;
|
|
1688
|
+
high_risk_factors.push("Drop column (pattern)");
|
|
1689
|
+
}
|
|
1690
|
+
if (/\bdelete\b.{0,30}\bcolumns?/i.test(text) && !high_risk_factors.includes("Drop column")) {
|
|
1691
|
+
risk_score += 30;
|
|
1692
|
+
high_risk_factors.push("Delete column (pattern)");
|
|
1693
|
+
}
|
|
1694
|
+
if (text.includes("delete") && (text.includes(" data") || text.includes("record"))) {
|
|
1695
|
+
risk_score += 25;
|
|
1696
|
+
high_risk_factors.push("Data deletion");
|
|
1697
|
+
}
|
|
1698
|
+
if (text.includes("new endpoint"))
|
|
1699
|
+
risk_score += 10;
|
|
1700
|
+
if (text.includes("new table"))
|
|
1701
|
+
risk_score += 10;
|
|
1702
|
+
if (text.includes("new kafka topic"))
|
|
1703
|
+
risk_score += 15;
|
|
1704
|
+
if (text.includes("new service"))
|
|
1705
|
+
risk_score += 20;
|
|
1706
|
+
if (text.includes("read only") || text.includes("display only"))
|
|
1707
|
+
risk_score -= 10;
|
|
1708
|
+
if (text.includes("no db change") || text.includes("no migration"))
|
|
1709
|
+
risk_score -= 15;
|
|
1710
|
+
if (text.includes("isolated change"))
|
|
1711
|
+
risk_score -= 10;
|
|
1712
|
+
if (text.includes("config change only"))
|
|
1713
|
+
risk_score -= 10;
|
|
1714
|
+
let rejected_pattern_risk = false;
|
|
1715
|
+
for (const rp of rejectedPatterns) {
|
|
1716
|
+
const rpKeywords = rp.keywords || (rp.pattern ? [rp.pattern] : []);
|
|
1717
|
+
for (const kw of rpKeywords) {
|
|
1718
|
+
if (kw && text.includes(kw.toLowerCase())) {
|
|
1719
|
+
rejected_pattern_risk = true;
|
|
1720
|
+
risk_score += 20;
|
|
1721
|
+
high_risk_factors.push(`Rejected pattern: ${rp.id || rp.name}`);
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
// ── Apply DNA-aware risk bonus ──────────────────────────────────────
|
|
1727
|
+
// Keyword scoring is blind to actual architecture. DNA bonus reflects real blast radius.
|
|
1728
|
+
risk_score += Math.min(dna_risk_bonus, 50); // cap at 50 to prevent double-stacking
|
|
1729
|
+
for (const note of dnaRiskNotes) {
|
|
1730
|
+
if (!high_risk_factors.includes(note))
|
|
1731
|
+
high_risk_factors.push(`[DNA] ${note}`);
|
|
1732
|
+
}
|
|
1733
|
+
const risk_verdict = risk_score <= 10 ? "LOW" : risk_score <= 30 ? "MEDIUM" : risk_score <= 50 ? "HIGH" : "CRITICAL";
|
|
1734
|
+
const reversibility = risk_score <= 20 ? "REVERSIBLE" : risk_score <= 40 ? "REVERSIBLE_WITH_EFFORT" : "DIFFICULT_TO_REVERSE";
|
|
1735
|
+
// PASS 5 - PHILOSOPHY VERDICT
|
|
1736
|
+
let philosophy_verdict = "PHILOSOPHY_PASS";
|
|
1737
|
+
const blocking_questions = [];
|
|
1738
|
+
const advisory_notes = [];
|
|
1739
|
+
if ((who_verdict === "UNCONFIRMED" && risk_verdict === "CRITICAL") ||
|
|
1740
|
+
(who_verdict === "UNCONFIRMED" && risk_verdict === "HIGH" && reversibility === "DIFFICULT_TO_REVERSE") ||
|
|
1741
|
+
(why_verdict === "NO_JUSTIFICATION" && (task_type === "feature" || task_type === "user_story")) ||
|
|
1742
|
+
(rejected_pattern_risk && risk_verdict === "CRITICAL")) {
|
|
1743
|
+
philosophy_verdict = "PHILOSOPHY_BLOCKED";
|
|
1744
|
+
}
|
|
1745
|
+
else if (who_verdict === "UNCONFIRMED" ||
|
|
1746
|
+
why_verdict === "NO_JUSTIFICATION" ||
|
|
1747
|
+
what_verdict === "UNDEFINED" ||
|
|
1748
|
+
(risk_verdict === "CRITICAL" && reversibility === "DIFFICULT_TO_REVERSE")) {
|
|
1749
|
+
philosophy_verdict = "PHILOSOPHY_FAIL";
|
|
1750
|
+
}
|
|
1751
|
+
else if (who_verdict === "UNCERTAIN" || why_verdict === "WEAK" || what_verdict === "VAGUE" || risk_verdict === "HIGH") {
|
|
1752
|
+
philosophy_verdict = "PHILOSOPHY_ADVISORY";
|
|
1753
|
+
}
|
|
1754
|
+
const allowed_next_action = philosophy_verdict === "PHILOSOPHY_PASS" ? "PROCEED" :
|
|
1755
|
+
philosophy_verdict === "PHILOSOPHY_ADVISORY" ? "ADVISORY_PROCEED" :
|
|
1756
|
+
philosophy_verdict === "PHILOSOPHY_FAIL" ? "STOP" : "ESCALATE";
|
|
1757
|
+
if (why_verdict === "NO_JUSTIFICATION" || why_verdict === "WEAK")
|
|
1758
|
+
blocking_questions.push({ category: "WHY", question: `What specific problem does "${title}" solve?`, impact: "Without a clear problem statement, the solution may be wrong.", action: "Add a problem statement with data, user pain, or regulatory requirement." });
|
|
1759
|
+
if (who_verdict === "UNCONFIRMED" || who_verdict === "UNCERTAIN")
|
|
1760
|
+
blocking_questions.push({ category: "WHO", question: "Who has explicitly confirmed this is needed?", impact: "Building unconfirmed features wastes sprint capacity.", action: "Get written confirmation from client or PM." });
|
|
1761
|
+
if (what_verdict === "UNDEFINED" || what_verdict === "VAGUE")
|
|
1762
|
+
blocking_questions.push({ category: "WHAT", question: "What are the acceptance criteria?", impact: "Vague stories produce vague software.", action: "Add Given/When/Then ACs." });
|
|
1763
|
+
if (risk_verdict === "CRITICAL" && reversibility === "DIFFICULT_TO_REVERSE")
|
|
1764
|
+
blocking_questions.push({ category: "RISK", question: `CRITICAL risk: ${high_risk_factors.join(", ")}. What is the rollback plan?`, impact: "This change may be impossible to revert.", action: "Define rollback plan and get explicit sign-off." });
|
|
1765
|
+
if (who_verdict === "UNCERTAIN")
|
|
1766
|
+
advisory_notes.push({ category: "WHO", note: "Decision maker not fully confirmed", suggestion: "Confirm with client or PM." });
|
|
1767
|
+
if (why_verdict === "WEAK")
|
|
1768
|
+
advisory_notes.push({ category: "WHY", note: "Weak justification", suggestion: "Strengthen with data or regulatory context." });
|
|
1769
|
+
if (what_verdict === "VAGUE")
|
|
1770
|
+
advisory_notes.push({ category: "WHAT", note: "ACs incomplete or vague", suggestion: "Add measurable ACs before coding." });
|
|
1771
|
+
if (risk_verdict === "HIGH")
|
|
1772
|
+
advisory_notes.push({ category: "RISK", note: `High risk: ${high_risk_factors.join(", ") || "multiple signals"}`, suggestion: "Review blast radius. Consider staged rollout." });
|
|
1773
|
+
let developer_message = "";
|
|
1774
|
+
const scoresSummary = `\nScores | WHY:${why_score} WHO:${who_score} WHAT:${what_score} RISK:${risk_score}${dna_risk_bonus > 0 ? ` (DNA+${dna_risk_bonus})` : ""}${dnaWarning}`;
|
|
1775
|
+
if (philosophy_verdict === "PHILOSOPHY_PASS") {
|
|
1776
|
+
developer_message = `[PHILOSOPHY PASS] ${work_item_id}\n\nWHY : ${why_verdict}${purpose_text ? " - " + purpose_text : ""}\nWHO : ${who_verdict}\nWHAT : ${what_verdict} — ${ac_count} AC(s)\nRISK : ${risk_verdict} (${reversibility})${high_risk_factors.length > 0 ? "\nRisk factors: " + high_risk_factors.join(", ") : ""}\n\nProceed to classify_task.${scoresSummary}`;
|
|
1777
|
+
}
|
|
1778
|
+
else if (philosophy_verdict === "PHILOSOPHY_ADVISORY") {
|
|
1779
|
+
developer_message = `[PHILOSOPHY ADVISORY] ${work_item_id}\n\nStory may proceed with caution. Note these concerns:\n\n` +
|
|
1780
|
+
advisory_notes.map((n) => ` [${n.category}] ${n.note}\n Suggestion: ${n.suggestion}`).join("\n\n") +
|
|
1781
|
+
`\n\nWHY:${why_verdict} | WHO:${who_verdict} | WHAT:${what_verdict} | RISK:${risk_verdict}\nProceed to classify_task. Record advisory notes in decisions.json.${scoresSummary}`;
|
|
1782
|
+
}
|
|
1783
|
+
else if (philosophy_verdict === "PHILOSOPHY_FAIL") {
|
|
1784
|
+
developer_message = `[PHILOSOPHY FAIL] ${work_item_id}\n\nImplementation BLOCKED. The following must be resolved before planning can begin:\n\n` +
|
|
1785
|
+
blocking_questions.map((q) => ` [${q.category}] ${q.question}\n Why: ${q.impact}\n Action: ${q.action}`).join("\n\n") +
|
|
1786
|
+
`\n\nDo NOT call classify_task. Re-run philosophy_check after resolving.${scoresSummary}`;
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
developer_message = `[PHILOSOPHY BLOCKED] ${work_item_id}\n\nESCALATION REQUIRED. ${risk_verdict} risk + ${who_verdict} authority.\n\n` +
|
|
1790
|
+
blocking_questions.map((q) => ` [${q.category}] ${q.question}\n Risk: ${q.impact}\n Required: ${q.action}`).join("\n\n") +
|
|
1791
|
+
`\n\nNO implementation or planning until explicit written sign-off from the decision maker.${scoresSummary}`;
|
|
1792
|
+
}
|
|
1793
|
+
const result = {
|
|
1794
|
+
work_item_id, title, task_type, philosophy_verdict, allowed_next_action,
|
|
1795
|
+
dna_assessment: {
|
|
1796
|
+
dna_available: dnaAvailable,
|
|
1797
|
+
dna_risk_bonus,
|
|
1798
|
+
dna_risk_notes: dnaRiskNotes,
|
|
1799
|
+
},
|
|
1800
|
+
passes: {
|
|
1801
|
+
why: { score: why_score, verdict: why_verdict, purpose_stated, purpose_text },
|
|
1802
|
+
who: { score: who_score, verdict: who_verdict },
|
|
1803
|
+
what: { score: what_score, verdict: what_verdict, ac_count, has_given_when_then: hasGWT, has_out_of_scope: hasOutOfScope, word_count: wordCount },
|
|
1804
|
+
risk: { score: risk_score, verdict: risk_verdict, reversibility, rejected_pattern_risk, high_risk_factors }
|
|
1805
|
+
},
|
|
1806
|
+
blocking_questions, advisory_notes, developer_message, scored_at: new Date().toISOString()
|
|
1807
|
+
};
|
|
1808
|
+
try {
|
|
1809
|
+
writeFile(philosophyFile, JSON.stringify(result, null, 2));
|
|
1810
|
+
}
|
|
1811
|
+
catch { }
|
|
1812
|
+
return appendTelemetry({ content: [{ type: "text", text: developer_message }] }, inputChars);
|
|
1813
|
+
});
|
|
1814
|
+
// --- Tool 10: classify_task --------------------------------------------------
|
|
1435
1815
|
server.tool("classify_task", "MANDATORY FIRST STEP for every task without exception. " +
|
|
1436
1816
|
"Classifies a task as BACKEND_ONLY, FRONTEND_ONLY, FULL_STACK, or EXTENSION_ONLY. " +
|
|
1437
1817
|
"Performs a breaking-change pre-scan against the project spec. " +
|
|
@@ -1561,8 +1941,9 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1561
1941
|
}
|
|
1562
1942
|
if (spec.frontend && spec.frontend.repo)
|
|
1563
1943
|
FRONTEND_SIGNALS.push(spec.frontend.repo.toLowerCase());
|
|
1564
|
-
|
|
1565
|
-
|
|
1944
|
+
const extSpec = spec.chrome_extension || spec["snf-browser-extn"] || spec.snf_browser_extn;
|
|
1945
|
+
if (extSpec && extSpec.repo)
|
|
1946
|
+
EXTENSION_SIGNALS.push(extSpec.repo.toLowerCase());
|
|
1566
1947
|
}
|
|
1567
1948
|
const backendFound = BACKEND_SIGNALS.filter(s => corpus.includes(s));
|
|
1568
1949
|
const frontendFound = FRONTEND_SIGNALS.filter(s => corpus.includes(s));
|
|
@@ -1587,8 +1968,9 @@ server.tool("classify_task", "MANDATORY FIRST STEP for every task without except
|
|
|
1587
1968
|
Object.keys(spec.microservices).forEach(k => services.push(k.toLowerCase()));
|
|
1588
1969
|
if (spec.frontend && spec.frontend.repo)
|
|
1589
1970
|
services.push(spec.frontend.repo.toLowerCase());
|
|
1590
|
-
|
|
1591
|
-
|
|
1971
|
+
const extSpec2 = spec.chrome_extension || spec["snf-browser-extn"] || spec.snf_browser_extn;
|
|
1972
|
+
if (extSpec2 && extSpec2.repo)
|
|
1973
|
+
services.push(extSpec2.repo.toLowerCase());
|
|
1592
1974
|
}
|
|
1593
1975
|
if (services.length === 0) {
|
|
1594
1976
|
services.push("sfn-iam-api", "sfn-events-api", "sfn-tenants-api", "sfn-policy-api", "sfn-gateway-api", "sfn-web-ui");
|
|
@@ -2182,7 +2564,7 @@ server.tool("analyze_impact", "Traces the SecuFusion inter-service call graph an
|
|
|
2182
2564
|
}
|
|
2183
2565
|
walkGraph(target, 0);
|
|
2184
2566
|
// ── Pass 2: Chrome extension check ────────────────────────────────────
|
|
2185
|
-
const extData = spec.chrome_extension || spec["sfn-chrome-ext"] || null;
|
|
2567
|
+
const extData = spec.chrome_extension || spec["sfn-chrome-ext"] || spec["snf-browser-extn"] || null;
|
|
2186
2568
|
let extensionHit = null;
|
|
2187
2569
|
if (extData) {
|
|
2188
2570
|
const extCalls = (extData.calls_endpoints || extData.calls || []).map(e => e.toLowerCase());
|
|
@@ -4116,6 +4498,10 @@ server.tool("reviewer_agent", "Context-aware architectural review agent — call
|
|
|
4116
4498
|
}
|
|
4117
4499
|
}
|
|
4118
4500
|
// PRAISE
|
|
4501
|
+
const extSpec3 = specData.chrome_extension || specData["snf-browser-extn"] || specData.snf_browser_extn;
|
|
4502
|
+
if (extSpec3 && extSpec3.repo) {
|
|
4503
|
+
expectedServices.push(extSpec3.repo);
|
|
4504
|
+
}
|
|
4119
4505
|
if (praises.length > 0) {
|
|
4120
4506
|
out += `### ✅ PRAISE\n`;
|
|
4121
4507
|
for (const f of praises) {
|
|
@@ -4253,7 +4639,7 @@ function readDna(ecosystemRoot) {
|
|
|
4253
4639
|
function writeDna(ecosystemRoot, dna) {
|
|
4254
4640
|
fs.writeFileSync(getDnaPath(ecosystemRoot), JSON.stringify(dna, null, 2));
|
|
4255
4641
|
}
|
|
4256
|
-
const VALID_EXTENSIONS = ['.java', '.ts', '.js', '.tsx', '.jsx', '.vue', '.sql', '.prisma', '.tf', '.yaml', '.yml', '.env', '.env.example', '.env.local', '.properties'];
|
|
4642
|
+
const VALID_EXTENSIONS = ['.java', '.ts', '.js', '.tsx', '.jsx', '.vue', '.sql', '.prisma', '.tf', '.yaml', '.yml', '.env', '.env.example', '.env.local', '.properties', '.json'];
|
|
4257
4643
|
function getAllSourceFiles(dirPath, repoName, arrayOfFiles = []) {
|
|
4258
4644
|
if (!fs.existsSync(dirPath))
|
|
4259
4645
|
return arrayOfFiles;
|
|
@@ -4278,7 +4664,7 @@ function getPolyRepoSourceFiles(ecosystemRoot) {
|
|
|
4278
4664
|
const children = fs.readdirSync(ecosystemRoot);
|
|
4279
4665
|
for (const child of children) {
|
|
4280
4666
|
const childPath = path.join(ecosystemRoot, child);
|
|
4281
|
-
if (fs.statSync(childPath).isDirectory() && (child.startsWith("sfn-") || child === "secufusion")) {
|
|
4667
|
+
if (fs.statSync(childPath).isDirectory() && (child.startsWith("sfn-") || child.startsWith("snf-") || child === "secufusion")) {
|
|
4282
4668
|
allFiles = allFiles.concat(getAllSourceFiles(childPath, child));
|
|
4283
4669
|
}
|
|
4284
4670
|
else if (fs.statSync(childPath).isDirectory() && ecosystemRoot === childPath) {
|
|
@@ -4294,6 +4680,7 @@ function performFullExtraction(ecosystemRoot) {
|
|
|
4294
4680
|
const events = [];
|
|
4295
4681
|
const nodeApis = [];
|
|
4296
4682
|
const frontend = [];
|
|
4683
|
+
const extension = [];
|
|
4297
4684
|
const infra = [];
|
|
4298
4685
|
const databases = [];
|
|
4299
4686
|
const security = [];
|
|
@@ -4307,6 +4694,7 @@ function performFullExtraction(ecosystemRoot) {
|
|
|
4307
4694
|
}
|
|
4308
4695
|
nodeApis.push(...parseNodeApis(fileObj.path, content, fileObj.repo));
|
|
4309
4696
|
frontend.push(...parseFrontend(fileObj.path, content, fileObj.repo));
|
|
4697
|
+
extension.push(...parseExtension(fileObj.path, content, fileObj.repo));
|
|
4310
4698
|
infra.push(...parseInfra(fileObj.path, content, fileObj.repo));
|
|
4311
4699
|
databases.push(...parseDatabase(fileObj.path, content, fileObj.repo));
|
|
4312
4700
|
security.push(...parseSecurity(fileObj.path, content, fileObj.repo));
|
|
@@ -4318,6 +4706,7 @@ function performFullExtraction(ecosystemRoot) {
|
|
|
4318
4706
|
dna.events = events;
|
|
4319
4707
|
dna.node_apis = nodeApis;
|
|
4320
4708
|
dna.frontend = frontend;
|
|
4709
|
+
dna.extension = extension;
|
|
4321
4710
|
dna.infra = infra;
|
|
4322
4711
|
dna.databases = databases;
|
|
4323
4712
|
dna.security = security;
|
|
@@ -4325,7 +4714,20 @@ function performFullExtraction(ecosystemRoot) {
|
|
|
4325
4714
|
// Apply Pattern Inference
|
|
4326
4715
|
dna.inferred_patterns = inferPatterns(dna);
|
|
4327
4716
|
writeDna(ecosystemRoot, dna);
|
|
4717
|
+
const hasBackend = (models.length > 0 || apis.length > 0 || nodeApis.length > 0);
|
|
4718
|
+
const hasFrontend = frontend.length > 0;
|
|
4719
|
+
const hasExtension = extension.length > 0;
|
|
4720
|
+
const missing = [];
|
|
4721
|
+
if (!hasBackend)
|
|
4722
|
+
missing.push("backend");
|
|
4723
|
+
if (!hasFrontend)
|
|
4724
|
+
missing.push("frontend");
|
|
4725
|
+
if (!hasExtension)
|
|
4726
|
+
missing.push("snf-browser-extn");
|
|
4727
|
+
const missingWarning = missing.length > 0 ? `\n\n[CRITICAL CORE CHECK] The following core components are missing from the DNA map: ${missing.join(', ')}. Please verify if this is expected.` : "";
|
|
4328
4728
|
return {
|
|
4729
|
+
missingWarning,
|
|
4730
|
+
extensionCount: extension.length,
|
|
4329
4731
|
modelsCount: models.length,
|
|
4330
4732
|
apisCount: apis.length + nodeApis.length,
|
|
4331
4733
|
eventsCount: events.length,
|
|
@@ -4396,7 +4798,7 @@ server.tool("extract_domain_models", "Parses files across the ecosystem to build
|
|
|
4396
4798
|
const ecosystemRoot = getEcosystemRoot(workspace_root);
|
|
4397
4799
|
const stats = performFullExtraction(ecosystemRoot);
|
|
4398
4800
|
return {
|
|
4399
|
-
content: [{ type: "text", text: `Extracted ${stats.modelsCount} domain models. Derived ${stats.inferredCount} architectural patterns
|
|
4801
|
+
content: [{ type: "text", text: `Extracted ${stats.modelsCount} domain models. Derived ${stats.inferredCount} architectural patterns.${stats.missingWarning}` }]
|
|
4400
4802
|
};
|
|
4401
4803
|
}
|
|
4402
4804
|
catch (e) {
|
|
@@ -4410,7 +4812,7 @@ server.tool("extract_api_endpoints", "Scans for REST/GraphQL endpoints across th
|
|
|
4410
4812
|
const ecosystemRoot = getEcosystemRoot(workspace_root);
|
|
4411
4813
|
const stats = performFullExtraction(ecosystemRoot);
|
|
4412
4814
|
return {
|
|
4413
|
-
content: [{ type: "text", text: `Extracted ${stats.apisCount} API controllers. Derived ${stats.inferredCount} architectural patterns
|
|
4815
|
+
content: [{ type: "text", text: `Extracted ${stats.apisCount} API controllers. Derived ${stats.inferredCount} architectural patterns.${stats.missingWarning}` }]
|
|
4414
4816
|
};
|
|
4415
4817
|
}
|
|
4416
4818
|
catch (e) {
|
|
@@ -4424,7 +4826,7 @@ server.tool("extract_event_topics", "Scans for event listener/producer usage acr
|
|
|
4424
4826
|
const ecosystemRoot = getEcosystemRoot(workspace_root);
|
|
4425
4827
|
const stats = performFullExtraction(ecosystemRoot);
|
|
4426
4828
|
return {
|
|
4427
|
-
content: [{ type: "text", text: `Extracted ${stats.eventsCount} event classes. Derived ${stats.inferredCount} architectural patterns
|
|
4829
|
+
content: [{ type: "text", text: `Extracted ${stats.eventsCount} event classes. Derived ${stats.inferredCount} architectural patterns.${stats.missingWarning}` }]
|
|
4428
4830
|
};
|
|
4429
4831
|
}
|
|
4430
4832
|
catch (e) {
|
package/package.json
CHANGED
package/commands/analyze.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Analyzes the current project to build its DNA Knowledge Graph
|
|
3
|
-
argument-hint: (no args)
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are the SecuFusion DNA Discovery Agent. Your goal is to analyze this ecosystem and build the Poly-Repo DNA Knowledge Graph.
|
|
7
|
-
|
|
8
|
-
The underlying MCP tools will automatically detect if you are inside a poly-repo structure. If they find sibling repositories, they will scan all of them simultaneously to build a cross-repo DNA file.
|
|
9
|
-
|
|
10
|
-
1. Call the `secufusion-dna_scan_repository_stack` tool to identify the framework and infrastructure across the ecosystem.
|
|
11
|
-
2. Call the `secufusion-dna_extract_domain_models` tool to find and parse entities and domain models across all repos.
|
|
12
|
-
3. Call the `secufusion-dna_extract_api_endpoints` tool to map out the API layer across all repos.
|
|
13
|
-
4. Call the `secufusion-dna_extract_event_topics` tool to map out event consumers and producers across all repos.
|
|
14
|
-
|
|
15
|
-
Once you have gathered this information, summarize your findings for the user. Do not delete any existing data in the knowledge graph.
|
package/commands/blast-radius.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Determines the impact of changing a specific component or file
|
|
3
|
-
argument-hint: <component-name-or-file-path>
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are the SecuFusion DNA Blast Radius Agent. Your goal is to determine the impact of changing the provided component or file across the entire Poly-Repo ecosystem.
|
|
7
|
-
|
|
8
|
-
1. Call the `secufusion-dna_query_knowledge_graph` tool with the query "Find all components, services, and APIs that depend on or consume {argument}".
|
|
9
|
-
2. Analyze the returned dependency chain, paying special attention to cross-repo dependencies (e.g., changing a DTO in `sfn-events-api` breaking a listener in `sfn-notification-api`).
|
|
10
|
-
3. Present a Cross-Repo Blast Radius Report to the user, categorizing the impact into:
|
|
11
|
-
- Impacted Microservices
|
|
12
|
-
- Impacted APIs
|
|
13
|
-
- Impacted Events/Consumers
|
|
14
|
-
- Impacted Databases
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Generates a Mermaid architecture diagram from the project DNA
|
|
3
|
-
argument-hint: (no args)
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are the SecuFusion DNA Architecture Agent. Your goal is to visualize the cross-service dependencies and architecture of this project.
|
|
7
|
-
|
|
8
|
-
1. Call the `secufusion-dna_query_knowledge_graph` tool with the query "Get all services, APIs, and event topics with their dependencies".
|
|
9
|
-
2. Based on the returned relationships, generate a comprehensive Mermaid diagram (`graph TD` or `graph LR`).
|
|
10
|
-
3. Render the diagram in your response, grouping components by bounded context or service if possible.
|
package/commands/map-services.md
DELETED
package/commands/watch-dna.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Starts the background watcher to continuously update the Poly-Repo DNA.
|
|
3
|
-
argument-hint: (no args)
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are the SecuFusion DNA Watcher Agent. Your goal is to start the background file watcher.
|
|
7
|
-
|
|
8
|
-
1. Call the `start_dna_watcher` tool.
|
|
9
|
-
2. Inform the user that the DNA Knowledge Graph is now actively monitoring the codebase in the background and will dynamically update whenever a developer saves a Java file.
|