pi-magi-theme 0.1.2 → 0.1.3

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
@@ -68,9 +68,11 @@ It extracts files, errors, decisions and open loops locally (no LLM calls), then
68
68
 
69
69
  | Unit | Nature | Looks at |
70
70
  |------|--------|----------|
71
- | MELCHIOR | PRAGMATIST | simplest working solution, effort vs value, reuse, YAGNI |
72
- | BALTHASAR | GUARDIAN | failure modes, security, operability, maintainability |
73
- | CASPAR | VISIONARY | reframing the problem, alternatives, DX, evolution |
71
+ | MELCHIOR | PRAGMATIST | what solves the problem, the simplest path, effort vs value, what already exists (in software: reuse, YAGNI, shipping) |
72
+ | BALTHASAR | GUARDIAN | what can go wrong and for whom, reversibility, hidden costs (in software: failure modes, security, operability) |
73
+ | CASPAR | VISIONARY | whether the question is framed right, alternatives, people's experience, long-term direction (in software: design, DX, evolution) |
74
+
75
+ Each nature is a lens, not a specialty, so the council answers any question, not only software ones. Every MAGI first answers the question, then judges it through its lens, naming concrete tools, numbers and scenarios from your question instead of generic advice. Votes: **APPROVE** = go ahead or clear recommendation; **CONDITIONAL** = only if the named conditions hold, or when information is missing (it says what it needs); **REJECT** = a concrete problem, with what to do instead. A MAGI never rejects because a topic is outside its nature. Answers come back in the language of your question.
74
76
 
75
77
  Each MAGI also gets the recent conversation as context. Full opinions are added to the chat (not sent to the agent).
76
78
 
@@ -857,41 +857,57 @@ function voteTone(v: string | null | undefined): "success" | "warning" | "error"
857
857
  return v === "APPROVE" ? "success" : v === "CONDITIONAL" ? "warning" : "error";
858
858
  }
859
859
 
860
- /** Three minds, three useful engineering viewpoints. */
860
+ /**
861
+ * Three minds, three lenses. A lens works on any subject (a nature, not a specialty), so no MAGI
862
+ * rejects a question just because it is not "its" topic; software is where each lens gets sharpest.
863
+ */
861
864
  const MAGI = [
862
865
  {
863
866
  unit: "MELCHIOR",
864
867
  nature: "PRAGMATIST",
865
868
  persona:
866
- "You are MELCHIOR, the pragmatist of the MAGI council: a senior engineer. Judge by: does it solve the actual problem, " +
867
- "the simplest solution that works, effort versus value, reuse of what already exists (stdlib, current stack, existing code), " +
868
- "time to ship. Call out over-engineering, speculative abstractions and unnecessary dependencies.",
869
+ "You are MELCHIOR, the pragmatist of the MAGI council. Your lens works on any subject: what actually solves the problem " +
870
+ "at hand, the simplest path that works, effort and cost versus value, what can be done now with what already exists, " +
871
+ "and what is unnecessary. In software this means: reuse the current stack and existing code, avoid over-engineering, " +
872
+ "speculative abstractions and extra dependencies, ship sooner.",
869
873
  },
870
874
  {
871
875
  unit: "BALTHASAR",
872
876
  nature: "GUARDIAN",
873
877
  persona:
874
- "You are BALTHASAR, the guardian of the MAGI council: a protective reliability and security architect. Judge by: failure modes, " +
875
- "security, data safety, operability (monitoring, rollback, being paged at 3am), maintainability for the team, " +
876
- "backward compatibility and hidden long-term costs. Say what will break and how to prevent it.",
878
+ "You are BALTHASAR, the guardian of the MAGI council. Your lens works on any subject: what can go wrong, how badly and " +
879
+ "for whom, whether the choice can be undone, which safety nets are missing, the hidden and long-term costs, and what " +
880
+ "must be protected. In software this means: failure modes, security, data safety, operability (monitoring, rollback, " +
881
+ "being paged at 3am), maintainability and backward compatibility.",
877
882
  },
878
883
  {
879
884
  unit: "CASPAR",
880
885
  nature: "VISIONARY",
881
886
  persona:
882
- "You are CASPAR, the visionary of the MAGI council: a creative, lateral-thinking architect. Judge by: is there a better framing of " +
883
- "the problem, more elegant or unconventional alternatives, developer and user experience, and how the design will evolve " +
884
- "over the next year. Always propose at least one alternative the other two would likely miss.",
887
+ "You are CASPAR, the visionary of the MAGI council. Your lens works on any subject: whether the question is framed right, " +
888
+ "better or unconventional alternatives, the experience of the people involved, and where the choice leads over time " +
889
+ "and what it unlocks. In software this means: design alternatives, developer and user experience, how the system " +
890
+ "evolves over the next year. Always name at least one concrete alternative the other two would likely miss.",
885
891
  },
886
892
  ] as const satisfies readonly { unit: MagiUnit; nature: string; persona: string }[];
887
893
 
888
- const MAGI_RULES = `You are one of the three MAGI deliberating on a question from a software engineer (coding, software architecture, infrastructure).
889
- Answer strictly from your own nature: the other two MAGI cover the other viewpoints.
890
- Reply in the same language as the question.
894
+ const MAGI_RULES = `You are one of the three MAGI. The council answers whatever the user asks: mostly software engineering, but not only.
895
+
896
+ Your nature is a lens, not a specialty, so competence is never a reason to reject. Whatever the subject, first work out the best answer to the question itself, then judge it through your lens. The other two MAGI cover the other lenses: stay in yours.
897
+
898
+ Be specific to this question. Every bullet must name something concrete from the question or the conversation: a tool, a number, a scenario, a step, a cost. Never write advice that would fit any question, such as "consider the trade-offs", "it depends", "ensure security" or "test properly".
899
+
900
+ How to vote:
901
+ - APPROVE: you would go ahead as asked, or you have a clear recommendation.
902
+ - CONDITIONAL: you would go ahead only if specific conditions hold, and you name them. When information is missing, vote CONDITIONAL and say exactly what you need to know and how each answer changes your recommendation.
903
+ - REJECT: your lens finds a concrete problem that makes the proposal a bad idea, and you say what to do instead. Never reject because the topic is outside software or outside your nature, or because details are missing.
904
+ For open questions (which one, how to), give your recommendation and vote on how confident you are in it.
905
+
906
+ Write in the language of the "Question for the MAGI", even though these instructions and the conversation may be in English.
891
907
  Output format, no preamble:
892
908
  VOTE: APPROVE | CONDITIONAL | REJECT
893
- - then at most 5 short bullet points (about 120 words total)
894
- For CONDITIONAL, the bullets must state the conditions. If the question is open-ended rather than yes/no, give your recommendation and vote on the direction the question implies.`;
909
+ - first bullet: your direct answer or recommendation
910
+ - then up to 4 bullets from your lens: 5 bullets at most in total, about 120 words`;
895
911
 
896
912
  interface MagiOpinion {
897
913
  unit: string;
@@ -965,6 +981,40 @@ function conversationExcerpt(ctx: ExtensionContext, maxChars = 6000): string {
965
981
  return joined.length > maxChars ? "…" + joined.slice(-maxChars) : joined;
966
982
  }
967
983
 
984
+ /** Common function words per language, used to name the reply language explicitly. */
985
+ const LANGUAGE_HINTS: readonly [string, readonly string[]][] = [
986
+ ["Italian", ["il", "lo", "la", "gli", "di", "che", "per", "non", "una", "con", "sono", "come", "perché", "è", "dovrei", "meglio", "mettiamo", "questo", "quale"]],
987
+ ["Spanish", ["el", "los", "las", "que", "para", "por", "es", "cómo", "debería", "mejor", "este", "cuál"]],
988
+ ["French", ["le", "les", "des", "est", "pour", "avec", "dois", "comment", "mieux", "ce", "quel"]],
989
+ ["German", ["der", "die", "das", "und", "ist", "nicht", "für", "mit", "ich", "soll", "wie", "besser"]],
990
+ ["English", ["the", "is", "should", "we", "for", "with", "and", "to", "of", "how", "which", "better"]],
991
+ ];
992
+
993
+ /**
994
+ * Guesses the question's language from function words; undefined when unsure.
995
+ * ponytail: stopword heuristic for five languages, swap in a real detector if other languages matter.
996
+ */
997
+ function guessLanguage(text: string): string | undefined {
998
+ const words = text.toLowerCase().match(/\p{L}+/gu) ?? [];
999
+ const scores = LANGUAGE_HINTS.map(([lang, hints]) => [lang, words.filter((w) => hints.includes(w)).length] as const).sort(
1000
+ (a, b) => b[1] - a[1],
1001
+ );
1002
+ const [best, second] = scores;
1003
+ return best![1] >= 2 && best![1] > second![1] ? best![0] : undefined;
1004
+ }
1005
+
1006
+ /** The user message each MAGI receives. The language reminder sits after the question, where the model reads it last. */
1007
+ function councilPrompt(project: string, excerpt: string, question: string): string {
1008
+ const lang = guessLanguage(question);
1009
+ const reminder = lang
1010
+ ? `(Write your whole answer in ${lang}, even if technical terms in the question are English.)`
1011
+ : "(Write your whole answer in the language of this question, even if technical terms in it are English.)";
1012
+ return (
1013
+ `Project: ${project}\n\nRecent conversation (context only, may be empty):\n<conversation>\n${excerpt}\n</conversation>\n\n` +
1014
+ `Question for the MAGI:\n${question}\n\n${reminder}`
1015
+ );
1016
+ }
1017
+
968
1018
  function resolveModel(ctx: ExtensionContext, ref?: string): Model<any> | undefined {
969
1019
  if (!ref) return ctx.model;
970
1020
  const slash = ref.indexOf("/");
@@ -1460,9 +1510,7 @@ export default function (pi: ExtensionAPI) {
1460
1510
 
1461
1511
  const cfg = loadMagiConfig();
1462
1512
  const project = (ctx.cwd ?? "").split("/").filter(Boolean).pop() ?? "";
1463
- const prompt =
1464
- `Project: ${project}\n\nRecent conversation (context only, may be empty):\n<conversation>\n${conversationExcerpt(ctx)}\n</conversation>\n\n` +
1465
- `Question for the MAGI:\n${question}`;
1513
+ const prompt = councilPrompt(project, conversationExcerpt(ctx), question);
1466
1514
 
1467
1515
  const controller = new AbortController();
1468
1516
  const opinions: (MagiOpinion | undefined)[] = MAGI.map(() => undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-magi-theme",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MAGI theme + extension for pi: Tree of Life header, three-model /magi council, golem tool animations, seven-seal context gauge, llama-swap telemetry",
5
5
  "keywords": [
6
6
  "pi-package",