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 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 `manage_project_spec`.
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
 
@@ -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
+
@@ -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
- // ─── Task management helpers ──────────────────────────────────────────────────
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secufusion-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "SecuFusion MCP server - developer workflow tooling with guardrails",
6
6
  "main": "mcp/dist/server.js",