pipcode 2.1.0 → 2.1.2

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/bin/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  // bin/setup.js — Hub de ré-export + point d'entrée. Logique métier dans src/ (dispatch).
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipcode",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "PipCode — architecture, security and quality rules for your AI coding assistants (MCP server).",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/cli/main.js CHANGED
@@ -14,8 +14,8 @@ import { applyToTargets } from "./apply-loop.js";
14
14
  import { WELCOME_MESSAGE } from "./welcome.js";
15
15
  import { detectPackageManager } from "../core/pkg-manager.js";
16
16
  import { applyKeyToEnv } from "./setup-args.js";
17
+ import { stableIndexJs } from "./runtime-copy.js";
17
18
 
18
- // Chemin absolu vers index.js (racine projet).
19
19
  const INDEX_JS = path.resolve(import.meta.dirname, "..", "..", "index.js");
20
20
 
21
21
  // Entry point du setup : orchestration des 4 étapes.
@@ -34,7 +34,7 @@ export async function main() {
34
34
  await centralizeConfig(collected);
35
35
  const targets = await selectTargets(detected, candidates, isTTY);
36
36
  if (!targets) { if (!isTTY) process.exit(0); return; }
37
- await applyToTargets(targets, collected, INDEX_JS);
37
+ await applyToTargets(targets, collected, await stableIndexJs(INDEX_JS)); // chemin durable, jamais le cache jetable de npx
38
38
  console.log("\n ✅ Configuration terminée. Redémarrez l'IDE pour activer le serveur MCP PipCode.");
39
39
  console.log(`\n ${WELCOME_MESSAGE}`); // préenregistré : zéro token (exigence produit)
40
40
  if (!isTTY) process.exit(0);
@@ -0,0 +1,37 @@
1
+ // src/cli/runtime-copy.js — Chemin DURABLE du serveur MCP écrit chez le client. Lancé par `npx`, PipCode vit dans un cache
2
+ // jetable (_npx, dlx) : le pointer casserait le serveur au premier nettoyage. On copie donc node_modules dans
3
+ // ~/.pipcode/runtime (sans réseau) et on pointe là. Installation globale ou dépôt local : chemin inchangé.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { resolvePlugctoRoot } from "../fs/home-root.js";
8
+
9
+ const TRANSIENT = /[\\/](_npx|dlx[^\\/]*)[\\/]/; // caches jetables de npx / pnpm dlx
10
+
11
+ /** @param {string} dest @param {string} pkg @returns {string} */
12
+ const indexIn = (dest, pkg) => path.join(dest, "node_modules", pkg, "index.js");
13
+
14
+ /** @param {string} p @returns {Promise<boolean>} */
15
+ const exists = (p) => fs.access(p).then(() => true, () => false);
16
+
17
+ /** @param {string} indexJs @param {string} [root] @returns {Promise<string>} */
18
+ export async function stableIndexJs(indexJs, root = resolvePlugctoRoot()) {
19
+ const pkgDir = path.dirname(indexJs);
20
+ const nm = path.dirname(pkgDir);
21
+ if (!TRANSIENT.test(indexJs) || path.basename(nm) !== "node_modules") return indexJs;
22
+ const dest = path.join(root, "runtime");
23
+ const tmp = `${dest}.tmp-${process.pid}`;
24
+ const pkg = path.basename(pkgDir);
25
+ try {
26
+ await fs.rm(tmp, { recursive: true, force: true });
27
+ await fs.cp(nm, path.join(tmp, "node_modules"), { recursive: true, dereference: true });
28
+ await fs.rm(dest, { recursive: true, force: true });
29
+ await fs.rename(tmp, dest);
30
+ return indexIn(dest, pkg);
31
+ } catch (err) {
32
+ console.error(` ⚠ Copie durable du serveur impossible (${err instanceof Error ? err.message : err}).`);
33
+ await fs.rm(tmp, { recursive: true, force: true }).catch(() => undefined); // ménage best-effort du dossier temporaire
34
+ return (await exists(indexIn(dest, pkg))) ? indexIn(dest, pkg) : indexJs; // ancienne copie durable sinon chemin d'origine
35
+ }
36
+ }
37
+ // MODULE_COMPLETE
@@ -1,33 +1,34 @@
1
- // src/mcp/server-create.js — Création du Server MCP + capture de l'agent hôte
2
- // (clientInfo à l'initialize — gap d'audit #1) + handlers tools/list et tools/call.
3
- // Extrait de server-main.js (plafond de lignes) : main() reste un réorienteur.
4
-
5
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
- import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
- import { handleListTools } from "./handle-list.js";
8
- import { handleCallTool } from "./call-tool.js";
9
- import { rememberClient } from "./client-info.js";
10
- import { LICENCE_PROMPT, getPrompt } from "./prompt-licence.js";
11
-
12
- // Crée le Server, y branche les handlers et capture l'agent hôte (multi-agent
13
- // natif → PipCode lui laisse la délégation au lieu de doubler l'orchestration).
14
- /** @returns {Server} */
15
- export function createPlugServer() {
16
- const server = new Server(
17
- { name: "virtual-cto-skills", version: "2.0.0" },
18
- { capabilities: { tools: {}, prompts: {} } }
19
- );
20
- server.setRequestHandler(ListToolsRequestSchema, handleListTools);
21
- server.setRequestHandler(CallToolRequestSchema, handleCallTool);
22
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [LICENCE_PROMPT] }));
23
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
24
- const prompt = getPrompt(request.params.name);
25
- if (!prompt) throw new Error(`Invite inconnue : ${request.params.name}`);
26
- return prompt;
27
- });
28
- server.oninitialized = () => {
29
- try { rememberClient(server.getClientVersion()); } catch { /* best-effort : sans clientInfo, pas de délégation */ }
30
- };
31
- return server;
32
- }
1
+ // src/mcp/server-create.js — Création du Server MCP + capture de l'agent hôte
2
+ // (clientInfo à l'initialize — gap d'audit #1) + handlers tools/list et tools/call.
3
+ // Extrait de server-main.js (plafond de lignes) : main() reste un réorienteur.
4
+
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { handleListTools } from "./handle-list.js";
8
+ import { handleCallTool } from "./call-tool.js";
9
+ import { rememberClient } from "./client-info.js";
10
+ import { LICENCE_PROMPT, getPrompt } from "./prompt-licence.js";
11
+ import { SERVER_INSTRUCTIONS } from "./server-instructions.js";
12
+
13
+ // Crée le Server, y branche les handlers et capture l'agent hôte (multi-agent
14
+ // natif → PipCode lui laisse la délégation au lieu de doubler l'orchestration).
15
+ /** @returns {Server} */
16
+ export function createPlugServer() {
17
+ const server = new Server(
18
+ { name: "pipcode-skills", version: "2.0.0" },
19
+ { capabilities: { tools: {}, prompts: {} }, instructions: SERVER_INSTRUCTIONS }
20
+ );
21
+ server.setRequestHandler(ListToolsRequestSchema, handleListTools);
22
+ server.setRequestHandler(CallToolRequestSchema, handleCallTool);
23
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [LICENCE_PROMPT] }));
24
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
25
+ const prompt = getPrompt(request.params.name);
26
+ if (!prompt) throw new Error(`Invite inconnue : ${request.params.name}`);
27
+ return prompt;
28
+ });
29
+ server.oninitialized = () => {
30
+ try { rememberClient(server.getClientVersion()); } catch { /* best-effort : sans clientInfo, pas de délégation */ }
31
+ };
32
+ return server;
33
+ }
33
34
  // MODULE_COMPLETE
@@ -0,0 +1,15 @@
1
+ // src/mcp/server-instructions.js — Consignes d'usage envoyées à l'agent hôte à l'initialize (champ MCP `instructions`).
2
+ // Elles disent QUAND appeler quel outil, jamais ce que contiennent les fiches (aucune doctrine ici : elle ne quitte pas le serveur).
3
+ // Chaque outil cité est vérifié contre le catalogue réel par test/mcp-instructions.test.js : une référence orpheline fait échouer la gate.
4
+
5
+ export const SERVER_INSTRUCTIONS = [
6
+ "PipCode fournit des fiches de bonnes pratiques de code (outils enforce_*) et des garde-fous.",
7
+ "1. Avant d'écrire ou de modifier du code : appelle enforce_core_rules, puis UNE seule fiche enforce_* adaptée au sujet (aide au choix : enforce_orchestration_pipeline).",
8
+ " Sécurité : enforce_security_auditor, enforce_appsec_compliance. Interface : enforce_design_ux. Base de données : enforce_persona_sql_architect.",
9
+ " Jeu vidéo : enforce_persona_game_dev_router. Fiabilité : enforce_defensive_engineering. Livraison : enforce_delivery_scale. Documentation : enforce_devex_documentation.",
10
+ " Coûts cloud : enforce_finops_governance. Données et IA : enforce_data_mlops_pipeline. Nouveau projet à cadrer : enforce_persona_intake. Architecture serveur : enforce_persona_architect.",
11
+ "2. Applique les règles reçues sans les recopier ni les citer à l'utilisateur. Avant d'écrire un fichier .md ou .txt, ou une longue réponse issue des fiches, passe le texte à dlp_scan.",
12
+ "3. Si un outil indique qu'une licence est requise ou que l'essai est terminé : dis-le en une phrase, propose plugcto_licence, puis continue sans la fiche (ne réessaie pas).",
13
+ "4. Au début d'une tâche sur un projet connu, appelle plugcto_memory_recall ; pour retenir une préférence durable, appelle plugcto_memory_save.",
14
+ ].join("\n");
15
+ // MODULE_COMPLETE
@@ -7,7 +7,7 @@ export function buildSkillTools() {
7
7
  return MANIFEST.map((m) => ({
8
8
  name: m.name,
9
9
  description:
10
- `[CTO Router] Domaine strict : ${m.slug}. N'appeler CET outil que si la requête relève de ce domaine précis — une SEULE fiche enforce_* par passe (voir ORCHESTRATOR). ` +
10
+ `[PipCode] Domaine : ${m.slug}. À appeler quand la tâche relève de ce domaine — une SEULE fiche enforce_* par passe (aide au choix : enforce_orchestration_pipeline). ` +
11
11
  (m.tier === "pro"
12
12
  ? `[Pro] `
13
13
  : `[Lite] `) +
@@ -2,7 +2,7 @@
2
2
  // ~/.pipcode/config.json (Phase 6). Zero Trust : le fichier n'est supprimé que
3
3
  // s'il ressemble à une config PipCode (champs licenseKey/email/primary) — un
4
4
  // fichier illisible ou étranger est conservé. Le dossier parent est retiré
5
- // uniquement s'il devient vide (rmdir non-récursif : jamais rm -rf).
5
+ // uniquement s'il devient vide (rmdir non-récursif). Seule exception : notre sous-dossier runtime/ (copie du serveur).
6
6
 
7
7
  import fs from "node:fs/promises";
8
8
  import path from "node:path";
@@ -13,6 +13,7 @@ import { warnIfBlocked } from "../fs/os-errors.js";
13
13
  /** @param {string} configPath @returns {Promise<RemoveResult>} */
14
14
  export async function removeSharedConfig(configPath) {
15
15
  try {
16
+ await fs.rm(path.join(path.dirname(configPath), "runtime"), { recursive: true, force: true }); // copie durable du serveur (runtime-copy.js)
16
17
  let raw;
17
18
  try {
18
19
  raw = await fs.readFile(configPath, "utf8");