secufusion-mcp 2.0.0 → 2.1.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.
- package/README.md +148 -1
- package/agents/planner.md +18 -7
- package/commands/sfn-plan.md +14 -0
- package/mcp/dist/server.js +194 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -838,12 +838,159 @@ Proceeding to plan presentation. No developer confirmation needed.
|
|
|
838
838
|
| **Guardrails** | Mixed soft/hard language | All `should` → `MUST`, all `avoid` → `FORBIDDEN`, linter errors explicitly blocking |
|
|
839
839
|
| **Cross-Task Intelligence** | Prose bullets | MANDATORY STEP 1-4 sequence + ❌ list |
|
|
840
840
|
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## 🏗️ v2.0.0 — Native Claude Plugin Architecture
|
|
845
|
+
|
|
846
|
+
`secufusion-mcp@2.0.0` is a **complete architectural rebuild** of the MCP server into a native Claude Plugin. It unifies the MCP server, slash commands, personas, and hooks into a single self-contained, portable package following the enterprise-grade `ml-specs` plugin standard.
|
|
847
|
+
|
|
848
|
+
### What Changed
|
|
849
|
+
|
|
850
|
+
| Area | Before (≤ 1.2.8) | After (2.0.0) |
|
|
851
|
+
|---|---|---|
|
|
852
|
+
| **Plugin type** | Standalone MCP server only | Native Claude Plugin (`.claude-plugin/plugin.json` + `.mcp.json`) |
|
|
853
|
+
| **Slash commands** | Disconnected — no wiring to server | Natively registered — appear in Claude IDE `/` command menu |
|
|
854
|
+
| **Agent personas** | Scattered globally in `.agents/` | Self-contained inside `agents/` within the plugin package |
|
|
855
|
+
| **Path portability** | Hardcoded absolute paths | Fully portable via `${CLAUDE_PLUGIN_ROOT}` |
|
|
856
|
+
| **DNA plugin** | Separate `secufusion-dna-plugin` package | Fully merged into `secufusion-mcp` |
|
|
857
|
+
| **TypeScript build** | Root-level compile | Isolated in `mcp/src/` → compiles to `mcp/dist/` |
|
|
858
|
+
| **Repo validation** | Not present | Reads `.secufusion-project-spec.json` to verify all mandatory repos are cloned |
|
|
859
|
+
| **Frontend/ext validation** | Not present | Checks `frontend.repo` and `chrome_extension.repo` from project spec |
|
|
860
|
+
|
|
861
|
+
### New Package Structure
|
|
862
|
+
|
|
863
|
+
```
|
|
864
|
+
secufusion-mcp/
|
|
865
|
+
├── .claude-plugin/
|
|
866
|
+
│ └── plugin.json ← Claude registers this as a native plugin
|
|
867
|
+
├── .mcp.json ← MCP server wired into the plugin (${CLAUDE_PLUGIN_ROOT} relative)
|
|
868
|
+
├── agents/ ← All agent personas (planner, coder, reviewer, claude, AGENTS.md)
|
|
869
|
+
├── commands/ ← All slash command definitions (markdown)
|
|
870
|
+
├── hooks/ ← Lifecycle hooks (knowledge-drift.sh)
|
|
871
|
+
├── mcp/
|
|
872
|
+
│ ├── src/
|
|
873
|
+
│ │ ├── server.ts ← Main MCP server logic
|
|
874
|
+
│ │ └── parsers/ ← Polyglot AST parsers (Java, TS, React, Config, Infra...)
|
|
875
|
+
│ ├── dist/ ← Compiled output (what npm ships)
|
|
876
|
+
│ └── tsconfig.json ← Isolated TypeScript config
|
|
877
|
+
├── scripts/
|
|
878
|
+
│ ├── sfn-pr-check.js ← Pre-PR mechanical guardrail runner
|
|
879
|
+
│ └── utils.js
|
|
880
|
+
└── package.json
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Merged: SecuFusion DNA Plugin
|
|
884
|
+
|
|
885
|
+
The previously separate `secufusion-dna-plugin` is now fully merged into `secufusion-mcp`. There is no longer a need to install or configure it separately. All DNA discovery tools are available natively:
|
|
886
|
+
|
|
887
|
+
- `scan_repository_stack` — discovers framework/stack and validates repos vs project spec
|
|
888
|
+
- `extract_domain_models` — maps JPA entities and domain objects via AST
|
|
889
|
+
- `extract_api_endpoints` — maps REST/GraphQL endpoints across all services
|
|
890
|
+
- `extract_event_topics` — maps Kafka producers and consumers
|
|
891
|
+
- `start_dna_watcher` — starts continuous background file watcher
|
|
892
|
+
|
|
893
|
+
### Mandatory Repository Validation
|
|
894
|
+
|
|
895
|
+
During `scan_repository_stack`, the server reads `.secufusion-project-spec.json` and cross-references:
|
|
896
|
+
- All keys under `"microservices"` (e.g., `sfn-auth-api`, `sfn-events-api`)
|
|
897
|
+
- The `"frontend.repo"` value (e.g., `sfn-web-ui`)
|
|
898
|
+
- The `"chrome_extension.repo"` value (e.g., `snf-browser-extn`)
|
|
899
|
+
|
|
900
|
+
If any of these are physically missing from your local workspace folder, a `[WARNING]` is emitted listing exactly which repositories need to be cloned before a complete DNA map can be built.
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
## ⚡ End-to-End Slash Command Workflow
|
|
905
|
+
|
|
906
|
+
Once installed as a native Claude Plugin, all commands appear natively in the Claude IDE `/` command picker. Here is the complete daily workflow:
|
|
907
|
+
|
|
908
|
+
### 🔁 Day Start — Setup & Discovery
|
|
909
|
+
|
|
910
|
+
| Command | When to run | What it does |
|
|
911
|
+
|---|---|---|
|
|
912
|
+
| `/sfn-init` | First thing in the morning, or on a new machine | Scans your entire workspace, validates all mandatory repos are cloned against `.secufusion-project-spec.json`, parses AST across all services (Java, TypeScript, React), and builds `.secufusion/dna.json` — the living knowledge graph |
|
|
913
|
+
| `/watch-dna` | Right after `/sfn-init` | Starts the `chokidar` background file watcher. From this point, every file save automatically re-triggers the relevant AST parser and keeps `dna.json` fresh — no manual re-runs needed |
|
|
914
|
+
|
|
915
|
+
> **Mono-folder rule:** Keep all microservices, frontend, and extension repos inside one parent folder (e.g., `C:\Users\Yash\Desktop\secufi_full\`). The agent uses the parent folder as the ecosystem root and scans all siblings automatically.
|
|
916
|
+
|
|
917
|
+
---
|
|
918
|
+
|
|
919
|
+
### 📋 Phase 1 — Plan a Ticket
|
|
920
|
+
|
|
921
|
+
| Command | When to run | What it does |
|
|
922
|
+
|---|---|---|
|
|
923
|
+
| `/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 coding patterns. Reads `dna.json` to determine which microservice owns the change. Outputs a structured `plan.md` with exact files to touch, rollback strategy, and breaking change scan. **Stops and waits for your green light.** |
|
|
924
|
+
|
|
925
|
+
> **Why it stops:** This enforces the non-negotiable Rule 3 — `STRICT YIELD`. The agent must not start coding until you explicitly say "proceed".
|
|
926
|
+
|
|
927
|
+
**Example:**
|
|
928
|
+
```
|
|
929
|
+
/sfn-plan TASK-2847: Add MFA enforcement for admin users on login
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
---
|
|
933
|
+
|
|
934
|
+
### 🛠️ Phase 2 — Build the Feature
|
|
935
|
+
|
|
936
|
+
| Command | When to run | What it does |
|
|
937
|
+
|---|---|---|
|
|
938
|
+
| `/sfn-code` | After you approve the plan | Agent switches to the `coder.md` persona and begins implementing **strictly according to the approved plan**. Enforces all coding patterns (correct `@Transactional` style, Tenant ID scoping, DTO mapping, Lombok style, exception handling). Every architectural mistake is immediately logged to `.rejected-patterns.json`. |
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
### ✅ Phase 3 — Review & Gate
|
|
943
|
+
|
|
944
|
+
| Command | When to run | What it does |
|
|
945
|
+
|---|---|---|
|
|
946
|
+
| `/sfn-review` | After coding is done, before opening a PR | Agent enters the adversarial `reviewer.md` persona. Triggers `run_pre_pr_checks` — a 3-tier AST-level gate: (1) Mechanical guardrails (tenant isolation, N+1 queries, hardcoded URLs), (2) AI file-by-file code review, (3) Context-aware task evaluation against your spec. **Blocks the PR if Tier 1 violations are found.** |
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
### 🔍 Phase 4 — Architecture Discovery
|
|
951
|
+
|
|
952
|
+
These commands can be run at any time to explore your codebase, independent of any active task.
|
|
953
|
+
|
|
954
|
+
| Command | When to run | What it does |
|
|
955
|
+
|---|---|---|
|
|
956
|
+
| `/blast-radius <component>` | Before refactoring a shared entity, API, or Kafka topic | Reads `dna.json` and calculates exactly which services, endpoints, and consumers will break if the given component is changed. Prevents accidental breaking changes. |
|
|
957
|
+
| `/map-architecture` | When onboarding a new dev or auditing the ecosystem | Generates a comprehensive bird's-eye view of all your services, domains, inter-service call graph, and Kafka topics sourced directly from the DNA graph. |
|
|
958
|
+
| `/analyze` | When debugging a cross-service issue or doing a deep-dive on a subsystem | Performs a deep-dive AST analysis of a specific area, generating detailed dependency and data-flow maps. |
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
### 📊 The Full SDLC Flow at a Glance
|
|
963
|
+
|
|
964
|
+
```
|
|
965
|
+
Morning
|
|
966
|
+
↓
|
|
967
|
+
/sfn-init ← Validate all repos, build DNA knowledge graph
|
|
968
|
+
↓
|
|
969
|
+
/watch-dna ← Background watcher keeps DNA fresh all day
|
|
970
|
+
↓
|
|
971
|
+
New ticket arrives
|
|
972
|
+
↓
|
|
973
|
+
/sfn-plan TASK-XXX ← Plan is written + presented → you say "proceed"
|
|
974
|
+
↓
|
|
975
|
+
/sfn-code ← Feature is implemented per plan, zero-trust guardrails active
|
|
976
|
+
↓
|
|
977
|
+
/sfn-review ← 3-tier AST gate → PASS or BLOCK with specific violations
|
|
978
|
+
↓
|
|
979
|
+
PR opened ✅
|
|
980
|
+
|
|
981
|
+
Need to investigate?
|
|
982
|
+
↓
|
|
983
|
+
/blast-radius ← Impact analysis before any structural change
|
|
984
|
+
/map-architecture ← Full ecosystem overview
|
|
985
|
+
/analyze ← Deep subsystem inspection
|
|
986
|
+
```
|
|
987
|
+
|
|
841
988
|
---
|
|
842
989
|
|
|
843
990
|
## Requirements
|
|
844
991
|
|
|
845
992
|
- **Node.js** >= 18.0.0
|
|
846
|
-
- An MCP-compatible AI client (Antigravity, Claude Desktop, Cursor, Cline, etc.)
|
|
993
|
+
- An MCP-compatible AI client (Antigravity IDE, Claude Desktop, Cursor, Cline, etc.)
|
|
847
994
|
|
|
848
995
|
---
|
|
849
996
|
|
package/agents/planner.md
CHANGED
|
@@ -15,15 +15,23 @@ STEP 1 — ALWAYS, unconditionally:
|
|
|
15
15
|
|
|
16
16
|
STEP 2 — if you need more details about a specific service not returned by prime_session:
|
|
17
17
|
→ call manage_project_spec(action: "get_service", service_name: <that service>)
|
|
18
|
+
|
|
19
|
+
STEP 3 — ALWAYS, for any work item that has been initialized:
|
|
20
|
+
→ call spec_read_intent(work_item_id: <active_id>)
|
|
21
|
+
This loads the WHY layer: business goal, PM owner, ACs, risk, and the Polyglot
|
|
22
|
+
Service Map (which lists services in Go/Python/Rust/any language even without AST parsers).
|
|
23
|
+
If the intent file does not exist yet, skip silently — do NOT block on this.
|
|
18
24
|
```
|
|
19
25
|
|
|
20
26
|
After these calls complete, you now know:
|
|
21
|
-
- All service ports, repos, domains
|
|
22
|
-
- Table ownership per service
|
|
23
|
-
- All inter-service REST calls
|
|
24
|
-
- Kafka topics (produces/consumes per service)
|
|
25
|
-
- Keycloak config and auth flow
|
|
26
|
-
- Coding patterns and golden rules
|
|
27
|
+
- All service ports, repos, domains (from AST + DNA)
|
|
28
|
+
- Table ownership per service (from AST)
|
|
29
|
+
- All inter-service REST calls (from AST)
|
|
30
|
+
- Kafka topics (produces/consumes per service) (from AST)
|
|
31
|
+
- Keycloak config and auth flow (from DNA)
|
|
32
|
+
- Coding patterns and golden rules (from DNA)
|
|
33
|
+
- **Business goal and PM intent** (from intent file — the WHY layer)
|
|
34
|
+
- **Polyglot service map** — Go/Python/Rust/any service involved even if no AST parser exists
|
|
27
35
|
|
|
28
36
|
You are NOW allowed to reason about the task. Not before.
|
|
29
37
|
|
|
@@ -34,6 +42,8 @@ You are NOW allowed to reason about the task. Not before.
|
|
|
34
42
|
- Cannot validate task scope against architecture
|
|
35
43
|
- Cannot enforce golden rules during classification
|
|
36
44
|
- Will hallucinate service details from memory
|
|
45
|
+
- **Will miss polyglot services with no AST parser** (Go, Python, Rust, etc.)
|
|
46
|
+
- **Will lose PM/business context** when making architectural tradeoffs
|
|
37
47
|
|
|
38
48
|
There are NO circumstances under which Phase 00 can be skipped, abbreviated, or substituted.
|
|
39
49
|
|
|
@@ -48,7 +58,8 @@ There are NO circumstances under which Phase 00 can be skipped, abbreviated, or
|
|
|
48
58
|
❌ Write any code
|
|
49
59
|
❌ Proceed to Phase 0.5 or any other phase
|
|
50
60
|
|
|
51
|
-
The ONLY tool calls permitted before Phase 00 completes are `prime_session` and `
|
|
61
|
+
The ONLY tool calls permitted before Phase 00 completes are `prime_session`, `manage_project_spec`, and `spec_read_intent`.
|
|
62
|
+
|
|
52
63
|
|
|
53
64
|
---
|
|
54
65
|
|
package/commands/sfn-plan.md
CHANGED
|
@@ -24,6 +24,18 @@ work_item_id: <the ticket ID or a slugified version of the description>
|
|
|
24
24
|
```
|
|
25
25
|
This creates the dedicated workspace folder at `.secufusion/tasks/<id>/` and begins tracking progress. Do not proceed until this call succeeds.
|
|
26
26
|
|
|
27
|
+
## Phase 1.5 — Capture Business Intent (The WHY Layer)
|
|
28
|
+
|
|
29
|
+
Immediately after `manage_task` initializes, call `spec_create_intent` with:
|
|
30
|
+
- `work_item_id`: same ID as above
|
|
31
|
+
- `title`: short human-readable title
|
|
32
|
+
- `business_goal`: **Ask the user** if not provided — "Why is this being built? What user or business problem does it solve?"
|
|
33
|
+
- `acceptance_criteria`: Given/When/Then ACs gathered from the ticket
|
|
34
|
+
- `services_involved`: **Polyglot service map** — list every service this touches, regardless of language (Java, Go, Python, TypeScript). This is how Go/Rust/Python services are tracked even without AST parsers.
|
|
35
|
+
- `risk`: business risk if not delivered
|
|
36
|
+
|
|
37
|
+
> 💡 This creates `.secufusion/intents/<num>-WI-<id>-<slug>.md` — a human-readable file capturing the WHY. The JSON task folder captures the HOW. Together they give a complete picture.
|
|
38
|
+
|
|
27
39
|
## Phase 2 — Load Context
|
|
28
40
|
|
|
29
41
|
1. Call `prime_session(work_item_id: <id>)` to efficiently load the project DNA state and relevant architectural context for this specific task.
|
|
@@ -49,8 +61,10 @@ Read `.agents/planner.md` for the Planner Persona instructions and produce a str
|
|
|
49
61
|
- **Architectural Decisions** (with rationale grounded in the DNA)
|
|
50
62
|
- **Risk Flags** (tenant isolation, N+1 risks, missing indexes, Kafka sync calls)
|
|
51
63
|
- **Test Plan**
|
|
64
|
+
- **Polyglot Impact** — for each service in the intent's Service Map that has no AST parser, note it explicitly and list what metadata is required (ports, topics, endpoints)
|
|
52
65
|
|
|
53
66
|
Save the plan to `.secufusion/tasks/<id>/plan.md`.
|
|
54
67
|
|
|
55
68
|
Present the plan and ask:
|
|
56
69
|
> 📋 Plan ready. Review `plan.md` and approve to begin coding with `/sfn:code`.
|
|
70
|
+
|
package/mcp/dist/server.js
CHANGED
|
@@ -504,9 +504,202 @@ server.tool("manage_project_spec", "Manages the .secufusion-project-spec.json fi
|
|
|
504
504
|
}
|
|
505
505
|
return appendTelemetry({ content: [{ type: "text", text: "Invalid action." }] }, inputChars);
|
|
506
506
|
});
|
|
507
|
-
// ───
|
|
507
|
+
// ─── Business Intent Layer ────────────────────────────────────────────────────
|
|
508
|
+
// Bridges the Polyglot Flexibility & Business Intent gaps.
|
|
509
|
+
// Every work item gets BOTH:
|
|
510
|
+
// - A JSON task folder (.secufusion/tasks/WI-XXXX/) → code truth (AST-driven)
|
|
511
|
+
// - A Markdown intent file (.secufusion/intents/WI-XXXX.md) → why truth (human-driven)
|
|
512
|
+
// prime_session merges both into a single context block.
|
|
513
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
514
|
+
const INTENTS_DIR = ".secufusion/intents";
|
|
508
515
|
const TASKS_DIR = ".secufusion/tasks";
|
|
509
516
|
const REGISTRY_FILE = ".secufusion/registry.json";
|
|
517
|
+
function slugify(text) {
|
|
518
|
+
return text
|
|
519
|
+
.toLowerCase()
|
|
520
|
+
.replace(/[:\s]+/g, "-")
|
|
521
|
+
.replace(/[^a-z0-9-]+/g, "")
|
|
522
|
+
.replace(/-+/g, "-")
|
|
523
|
+
.replace(/^-|-$/g, "")
|
|
524
|
+
.slice(0, 60);
|
|
525
|
+
}
|
|
526
|
+
function nextIntentNumber() {
|
|
527
|
+
const intentsRoot = resolve(INTENTS_DIR);
|
|
528
|
+
if (!fs.existsSync(intentsRoot))
|
|
529
|
+
return "0001";
|
|
530
|
+
const files = fs.readdirSync(intentsRoot).filter(f => f.endsWith(".md"));
|
|
531
|
+
if (files.length === 0)
|
|
532
|
+
return "0001";
|
|
533
|
+
const nums = files
|
|
534
|
+
.map(f => parseInt(f.split("-")[0], 10))
|
|
535
|
+
.filter(n => !isNaN(n));
|
|
536
|
+
const max = nums.length > 0 ? Math.max(...nums) : 0;
|
|
537
|
+
return String(max + 1).padStart(4, "0");
|
|
538
|
+
}
|
|
539
|
+
function parseIntentFrontmatter(raw) {
|
|
540
|
+
const result = {};
|
|
541
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
542
|
+
if (!match)
|
|
543
|
+
return result;
|
|
544
|
+
for (const line of match[1].split("\n")) {
|
|
545
|
+
const [key, ...rest] = line.split(":");
|
|
546
|
+
if (key && rest.length)
|
|
547
|
+
result[key.trim()] = rest.join(":").trim();
|
|
548
|
+
}
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
// ─── Tool: spec_next_number ───────────────────────────────────────────────────
|
|
552
|
+
server.tool("spec_next_number", "Returns the next available 4-digit spec/intent number (e.g. 0031). Call before creating a new intent doc to ensure no collisions.", {}, async () => {
|
|
553
|
+
const inputChars = 0;
|
|
554
|
+
const next = nextIntentNumber();
|
|
555
|
+
return appendTelemetry({
|
|
556
|
+
content: [{ type: "text", text: JSON.stringify({ next_number: next, intents_dir: resolve(INTENTS_DIR) }, null, 2) }]
|
|
557
|
+
}, inputChars);
|
|
558
|
+
});
|
|
559
|
+
// ─── Tool: spec_create_intent ─────────────────────────────────────────────────
|
|
560
|
+
server.tool("spec_create_intent", "Creates a structured Markdown intent file in .secufusion/intents/ for a work item. This captures the WHY behind the code: business goal, PM sign-off, acceptance criteria, risk, and polyglot service map. Complements manage_task (which captures the HOW). Call immediately after manage_task action=initialize.", {
|
|
561
|
+
work_item_id: z.string().describe("Azure DevOps work item ID, e.g. '2847'."),
|
|
562
|
+
title: z.string().describe("Short feature/bug title."),
|
|
563
|
+
business_goal: z.string().describe("1–3 sentences: why is this being built? What user or business problem does it solve?"),
|
|
564
|
+
pm_owner: z.string().optional().describe("Product Manager name or alias responsible for this feature."),
|
|
565
|
+
ticket_url: z.string().optional().describe("Full URL to the Azure DevOps / Jira ticket."),
|
|
566
|
+
acceptance_criteria: z.array(z.string()).describe("Given/When/Then ACs. Each string is one AC."),
|
|
567
|
+
out_of_scope: z.array(z.string()).optional().describe("Things explicitly NOT covered by this work item."),
|
|
568
|
+
risk: z.string().optional().describe("Business risk if not delivered. e.g. 'SOC 2 audit failure', 'Revenue at risk', 'Low'."),
|
|
569
|
+
services_involved: z.array(z.object({
|
|
570
|
+
name: z.string().describe("Service name, e.g. 'notification-api'"),
|
|
571
|
+
language: z.string().describe("Primary language, e.g. 'TypeScript', 'Java', 'Go', 'Python'"),
|
|
572
|
+
role: z.string().describe("Role in this feature, e.g. 'produces Kafka event', 'REST consumer'")
|
|
573
|
+
})).optional().describe("Polyglot service map — lists every service involved regardless of language. This is the polyglot bridge."),
|
|
574
|
+
notes: z.string().optional().describe("Any additional context, constraints, or dependencies.")
|
|
575
|
+
}, async ({ work_item_id, title, business_goal, pm_owner, ticket_url, acceptance_criteria, out_of_scope, risk, services_involved, notes }) => {
|
|
576
|
+
const inputChars = JSON.stringify(arguments).length;
|
|
577
|
+
const num = nextIntentNumber();
|
|
578
|
+
const slug = slugify(title);
|
|
579
|
+
const filename = `${num}-WI-${work_item_id}-${slug}.md`;
|
|
580
|
+
const intentsRoot = resolve(INTENTS_DIR);
|
|
581
|
+
const filePath = path.join(intentsRoot, filename);
|
|
582
|
+
const acBlock = acceptance_criteria
|
|
583
|
+
.map((ac, i) => `- [ ] **AC${i + 1}:** ${ac}`)
|
|
584
|
+
.join("\n");
|
|
585
|
+
const oosBlock = (out_of_scope || []).length > 0
|
|
586
|
+
? (out_of_scope || []).map(s => `- ${s}`).join("\n")
|
|
587
|
+
: "_None specified._";
|
|
588
|
+
const servicesBlock = (services_involved || []).length > 0
|
|
589
|
+
? (services_involved || []).map(s => `| ${s.name} | ${s.language} | ${s.role} |`).join("\n")
|
|
590
|
+
: "| _(none specified)_ | — | — |";
|
|
591
|
+
const now = new Date().toISOString();
|
|
592
|
+
const content = `---
|
|
593
|
+
work_item_id: ${work_item_id}
|
|
594
|
+
title: ${title}
|
|
595
|
+
status: Draft
|
|
596
|
+
number: ${num}
|
|
597
|
+
pm_owner: ${pm_owner || "TBD"}
|
|
598
|
+
ticket_url: ${ticket_url || "TBD"}
|
|
599
|
+
risk: ${risk || "TBD"}
|
|
600
|
+
created_at: ${now}
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
# [WI-${work_item_id}] ${title}
|
|
604
|
+
|
|
605
|
+
## 🎯 Business Goal
|
|
606
|
+
|
|
607
|
+
${business_goal}
|
|
608
|
+
|
|
609
|
+
## 📋 Acceptance Criteria
|
|
610
|
+
|
|
611
|
+
${acBlock}
|
|
612
|
+
|
|
613
|
+
## 🚫 Out of Scope
|
|
614
|
+
|
|
615
|
+
${oosBlock}
|
|
616
|
+
|
|
617
|
+
## 🌐 Service Map (Polyglot Bridge)
|
|
618
|
+
|
|
619
|
+
| Service | Language | Role in this Feature |
|
|
620
|
+
|---------|----------|----------------------|
|
|
621
|
+
${servicesBlock}
|
|
622
|
+
|
|
623
|
+
## ⚠️ Risk
|
|
624
|
+
|
|
625
|
+
${risk || "_Not assessed._"}
|
|
626
|
+
|
|
627
|
+
## 📝 Notes
|
|
628
|
+
|
|
629
|
+
${notes || "_None._"}
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
_Intent file auto-generated by secufusion-mcp. Edit freely — this is the human layer._
|
|
633
|
+
`;
|
|
634
|
+
writeFile(filePath, content);
|
|
635
|
+
return appendTelemetry({
|
|
636
|
+
content: [{
|
|
637
|
+
type: "text",
|
|
638
|
+
text: `✅ Intent spec created.\n\n**File:** ${filePath}\n**Number:** ${num}\n**Slug:** ${slug}\n\nThis file captures the WHY. The JSON task folder (.secufusion/tasks/) captures the HOW.\nBoth will be merged by prime_session for a complete context.\n\n---\n${content}`
|
|
639
|
+
}]
|
|
640
|
+
}, inputChars);
|
|
641
|
+
});
|
|
642
|
+
// ─── Tool: spec_read_intent ───────────────────────────────────────────────────
|
|
643
|
+
server.tool("spec_read_intent", "Read the intent (WHY) file for a work item. Returns business goal, PM owner, acceptance criteria status, polyglot service map, and risk. Use alongside prime_session to get a complete picture of code truth + business intent.", {
|
|
644
|
+
work_item_id: z.string().describe("Work item ID to look up, e.g. '2847'."),
|
|
645
|
+
update_ac: z.object({
|
|
646
|
+
index: z.number().describe("0-based index of the AC to tick"),
|
|
647
|
+
checked: z.boolean().describe("true = mark as done (- [x]), false = uncheck (- [ ])")
|
|
648
|
+
}).optional().describe("Optionally tick or untick an acceptance criterion checkbox in the markdown.")
|
|
649
|
+
}, async ({ work_item_id, update_ac }) => {
|
|
650
|
+
const inputChars = JSON.stringify({ work_item_id, update_ac }).length;
|
|
651
|
+
const intentsRoot = resolve(INTENTS_DIR);
|
|
652
|
+
if (!fs.existsSync(intentsRoot)) {
|
|
653
|
+
return appendTelemetry({ isError: true, content: [{ type: "text", text: `No intents directory found at ${intentsRoot}. Run spec_create_intent first.` }] }, inputChars);
|
|
654
|
+
}
|
|
655
|
+
const files = fs.readdirSync(intentsRoot).filter(f => f.endsWith(".md"));
|
|
656
|
+
const match = files.find(f => f.includes(`WI-${work_item_id}`));
|
|
657
|
+
if (!match) {
|
|
658
|
+
return appendTelemetry({ isError: true, content: [{ type: "text", text: `No intent file found for WI-${work_item_id} in ${intentsRoot}.\nCreate one with spec_create_intent.` }] }, inputChars);
|
|
659
|
+
}
|
|
660
|
+
const filePath = path.join(intentsRoot, match);
|
|
661
|
+
let raw = fs.readFileSync(filePath, "utf-8");
|
|
662
|
+
if (update_ac !== undefined) {
|
|
663
|
+
const acLines = raw.split("\n");
|
|
664
|
+
let acCount = 0;
|
|
665
|
+
for (let i = 0; i < acLines.length; i++) {
|
|
666
|
+
if (acLines[i].match(/^- \[[ x]\] \*\*AC\d+/)) {
|
|
667
|
+
if (acCount === update_ac.index) {
|
|
668
|
+
acLines[i] = acLines[i].replace(/^- \[[ x]\]/, update_ac.checked ? "- [x]" : "- [ ]");
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
acCount++;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
raw = acLines.join("\n");
|
|
675
|
+
fs.writeFileSync(filePath, raw, "utf-8");
|
|
676
|
+
}
|
|
677
|
+
const frontmatter = parseIntentFrontmatter(raw);
|
|
678
|
+
const acMatches = [...raw.matchAll(/^- \[([ x])\] \*\*AC(\d+):\*\* (.+)$/gm)];
|
|
679
|
+
const acs = acMatches.map(m => ({
|
|
680
|
+
index: parseInt(m[2], 10) - 1,
|
|
681
|
+
done: m[1] === "x",
|
|
682
|
+
text: m[3]
|
|
683
|
+
}));
|
|
684
|
+
const done = acs.filter(a => a.done).length;
|
|
685
|
+
const summary = {
|
|
686
|
+
work_item_id,
|
|
687
|
+
file: match,
|
|
688
|
+
status: frontmatter.status || "Draft",
|
|
689
|
+
title: frontmatter.title || "",
|
|
690
|
+
pm_owner: frontmatter.pm_owner || "TBD",
|
|
691
|
+
risk: frontmatter.risk || "TBD",
|
|
692
|
+
ticket_url: frontmatter.ticket_url || "",
|
|
693
|
+
ac_progress: `${done}/${acs.length} complete`,
|
|
694
|
+
acceptance_criteria: acs,
|
|
695
|
+
};
|
|
696
|
+
return appendTelemetry({
|
|
697
|
+
content: [{
|
|
698
|
+
type: "text",
|
|
699
|
+
text: `## Intent: WI-${work_item_id}\n\n\`\`\`json\n${JSON.stringify(summary, null, 2)}\n\`\`\`\n\n---\n### Full Intent Document\n\n${raw}`
|
|
700
|
+
}]
|
|
701
|
+
}, inputChars);
|
|
702
|
+
});
|
|
510
703
|
function resolveTaskFolder(workItemId, tasksRoot, registryData) {
|
|
511
704
|
const registry = registryData || readRegistry();
|
|
512
705
|
const entry = registry.find(t => t["work_item_id"] === workItemId);
|