@stratta/mcp 1.18.1 → 1.20.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
@@ -198,18 +198,18 @@ package announces 59 of them; the remote connector serves 61: everything minus
198
198
 
199
199
  **Read** (10 tools — query the norms of your workspace):
200
200
 
201
- | Tool | Purpose |
202
- | ----------------- | --------------------------------------------------------------------------------------- |
203
- | `get_methodology` | Behavioural contract: persona, workflow, meta-routing hints, answer rules. Call first. |
204
- | `whoami` | Which organisation, role and plan this connection reads as, and how many norms it sees. |
205
- | `list_norms` | List all norms published in your workspace (code, year, title, language, coverage). |
206
- | `get_toc` | Hierarchical TOC for a norm (default `maxDepth=1` = chapters). |
207
- | `get_subtree` | Drill into a chapter/section subtree (`path` + `maxDepth`). |
208
- | `get_section` | Full enriched content of a section (formulas, tables, figures, cross-refs). |
209
- | `search_in_norm` | Keyword search inside a norm. |
210
- | `search_corpus` | Keyword search across every norm of the workspace, grouped by norm. |
211
- | `get_figure` | Retrieve a figure inline (base64 ImageContent). |
212
- | `get_cross_refs` | Outgoing cross-refs from a section to other norms. |
201
+ | Tool | Purpose |
202
+ | ----------------- | ------------------------------------------------------------------------------------------------ |
203
+ | `get_methodology` | Behavioural contract: persona, workflow, meta-routing hints, answer rules. Call first. |
204
+ | `whoami` | Which organisation, role and plan this connection reads as, and how many norms it sees. |
205
+ | `list_norms` | List the norms of your workspace, filtered by `query` (code or title words), bounded by `limit`. |
206
+ | `get_toc` | Hierarchical TOC for a norm (default `maxDepth=1` = chapters). |
207
+ | `get_subtree` | Drill into a chapter/section subtree (`path` + `maxDepth`). |
208
+ | `get_section` | Full enriched content of a section (formulas, tables, figures, cross-refs). |
209
+ | `search_in_norm` | Keyword search inside a norm. |
210
+ | `search_corpus` | Keyword search across every norm of the workspace, grouped by norm. |
211
+ | `get_figure` | Retrieve a figure inline (base64 ImageContent). |
212
+ | `get_cross_refs` | Outgoing cross-refs from a section to other norms. |
213
213
 
214
214
  **Site** (10 tools — what public Swiss registers know about a plot, and the site sheet as the app shows it):
215
215
 
@@ -13,7 +13,7 @@ export type CatalogEntry = {
13
13
  transports: Transport[];
14
14
  params: Record<string, string>;
15
15
  };
16
- export declare const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. Call `get_methodology` before the first substantive question of a conversation: it carries the navigation order, the citation rules and the dossier policy. Never answer from memory a question these documents are meant to settle; a norm absent from `list_norms` cannot be read.";
16
+ export declare const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. Call `get_methodology` before the first substantive question of a conversation: it carries the navigation order, the citation rules and the dossier policy. A question these documents are meant to settle is answered from them, not from memory; a norm absent from `list_norms` cannot be read.";
17
17
  export declare const CATALOG: CatalogEntry[];
18
18
  export type PromptArg = {
19
19
  name: string;
@@ -1,11 +1,11 @@
1
1
  // Generated by scripts/build-mcp-catalog.mjs from convex/mcpCatalog.ts.
2
2
  // Do not edit: change the source and run the script.
3
- export const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. Call `get_methodology` before the first substantive question of a conversation: it carries the navigation order, the citation rules and the dossier policy. Never answer from memory a question these documents are meant to settle; a norm absent from `list_norms` cannot be read.";
3
+ export const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. Call `get_methodology` before the first substantive question of a conversation: it carries the navigation order, the citation rules and the dossier policy. A question these documents are meant to settle is answered from them, not from memory; a norm absent from `list_norms` cannot be read.";
4
4
  export const CATALOG = [
5
5
  {
6
6
  "name": "get_methodology",
7
7
  "title": "Working method",
8
- "description": "Read the working contract this server expects an agent to follow: how to walk a norm from its table of contents down to a clause, how to cite what it reads, and when to record something in a project dossier. Call it once, before the first technical answer of a conversation, and follow it for the rest of the conversation.",
8
+ "description": "Read the working contract this server expects an agent to follow: how to walk a norm from its table of contents down to a clause, how to cite what it reads, and when to record something in a project dossier. Use this at the start of a conversation that will rely on the documents of the organization; its rules hold for the rest of that conversation.",
9
9
  "annotations": {
10
10
  "readOnlyHint": true,
11
11
  "destructiveHint": false,
@@ -37,7 +37,7 @@ export const CATALOG = [
37
37
  {
38
38
  "name": "list_norms",
39
39
  "title": "Available norms",
40
- "description": "List the norms and reference documents (standards, laws, regulations) published in the signed-in organization, with code, year, title, language and coverage (how much of the norm was ingested). This is the first call of any search: a norm absent from this list cannot be read and must not be answered from memory. A demo entry is fictional and never a technical reference; a sparse entry carries a notice to relay.",
40
+ "description": "List the norms and reference documents (standards, laws, regulations) published in the signed-in organization, with code, year, title, edition, language and coverage (how much of the norm was ingested). Use this to check whether a norm is held, which editions are readable, or what a norm covers: a norm absent from this organization cannot be read here. Pass `query` with a code or words of the title; a large corpus is listed in part, and `total` says how many norms there are. A demo entry is fictional and never a technical reference; a sparse entry carries a notice to relay.",
41
41
  "annotations": {
42
42
  "readOnlyHint": true,
43
43
  "destructiveHint": false,
@@ -48,7 +48,10 @@ export const CATALOG = [
48
48
  "stdio",
49
49
  "remote"
50
50
  ],
51
- "params": {}
51
+ "params": {
52
+ "query": "A code or words of the title or scope, accents optional, e.g. \"SIA 267\" or \"geotechnique\". Every word must match. With a query, each norm comes with its scope.",
53
+ "limit": "Maximum number of norms listed, 1 to 500. Default 60."
54
+ }
52
55
  },
53
56
  {
54
57
  "name": "get_toc",
@@ -138,7 +141,7 @@ export const CATALOG = [
138
141
  {
139
142
  "name": "search_corpus",
140
143
  "title": "Search all norms",
141
- "description": "Full-text search across every norm the organization can read, grouped by norm, heading hits from any norm ahead of body hits. Use it first when the question does not name its norm: it tells which norms speak of the subject before any table of contents is opened. Same matching as search_in_norm. Returns, per norm, its code and title and a few hits; continue with get_section on the paths that look relevant.",
144
+ "description": "Full-text search across every norm the organization can read, grouped by norm, heading hits from any norm ahead of body hits. Use this when the question does not name its norm: it tells which norms speak of the subject before any table of contents is opened. Same matching as search_in_norm. Returns, per norm, its code and title and a few hits; continue with get_section on the paths that look relevant.",
142
145
  "annotations": {
143
146
  "readOnlyHint": true,
144
147
  "destructiveHint": false,
@@ -197,7 +200,7 @@ export const CATALOG = [
197
200
  {
198
201
  "name": "show_citations",
199
202
  "title": "Show cited clauses",
200
- "description": "Show the person the clauses your answer rests on, as cards they can open and check against the norm. Call it once, after answering, with the sections you actually read with get_section and cited, in the order you cited them. It reads nothing new: it never replaces get_section, and a clause you did not read must not be listed.",
203
+ "description": "Use this when an answer rests on clauses read with get_section and the person should be able to check them: shows those clauses as cards they can open against the norm, once per answer, in the order they were cited. It reads nothing new and does not replace get_section; only clauses actually read belong in it.",
201
204
  "annotations": {
202
205
  "readOnlyHint": true,
203
206
  "destructiveHint": false,
@@ -488,7 +491,7 @@ export const CATALOG = [
488
491
  {
489
492
  "name": "save_finding",
490
493
  "title": "Record evidence",
491
- "description": "Record one piece of evidence in a dossier, as you work rather than in a batch at the end. Kinds: reference (what a norm says; always give normCode, sectionPath and page, or it is not a reference), hypothesis (the value the user retains and why, value in value and reasoning in detail), observation (what the site, a borehole or a survey showed, with the date). One clause, one value or one fact per call, in one or two sentences. Pass questionId to file it under the question it serves; without it the entry waits unclassified. Set confidence whenever a value is at stake: established, judgement or to_confirm. Questions go through open_question and decisions through record_decision, not here.",
494
+ "description": "Use this when a piece of evidence turns up during the work: records it in a dossier, one at a time rather than in a batch at the end. Kinds: reference (what a norm says; always give normCode, sectionPath and page, or it is not a reference), hypothesis (the value the user retains and why, value in value and reasoning in detail), observation (what the site, a borehole or a survey showed, with the date). One clause, one value or one fact per call, in one or two sentences. Pass questionId to file it under the question it serves; without it the entry waits unclassified. Set confidence whenever a value is at stake: established, judgement or to_confirm. Questions go through open_question and decisions through record_decision, not here.",
492
495
  "annotations": {
493
496
  "readOnlyHint": false,
494
497
  "destructiveHint": false,
@@ -518,7 +521,7 @@ export const CATALOG = [
518
521
  {
519
522
  "name": "record_decision",
520
523
  "title": "Record a decision",
521
- "description": "Record what the user retained: the decision in one line, why, and the clause it rests on. Pass questionId when the question already exists; otherwise pass dossierId and question (what is settled, in one line) and the question is opened on the spot, already decided. Name the retained option when the question listed some; put the number in value and set confidence when the decision is a value. Only after the user confirmed the decision, never on your own reasoning. Returns entryId.",
524
+ "description": "Use this when the user has settled a point: records the decision in one line, why, and the clause it rests on. Pass questionId when the question already exists; otherwise pass dossierId and question (what is settled, in one line) and the question is opened on the spot, already decided. Name the retained option when the question listed some; put the number in value and set confidence when the decision is a value. Meant for decisions the user confirmed, not for conclusions of the agent alone. Returns entryId.",
522
525
  "annotations": {
523
526
  "readOnlyHint": false,
524
527
  "destructiveHint": false,
@@ -548,7 +551,7 @@ export const CATALOG = [
548
551
  {
549
552
  "name": "load_dossier",
550
553
  "title": "Load a dossier",
551
- "description": "Reload a dossier: its questions with their evidence, options and decisions, open questions first, then the evidence not yet filed under a question, then the comments a colleague left. Do this before answering whenever the user returns to a project already named, otherwise a decision already taken gets taken again. The name is enough; the id is optional.",
554
+ "description": "Reload a dossier: its questions with their evidence, options and decisions, open questions first, then the evidence not yet filed under a question, then the comments a colleague left. Use this when the user returns to a project already named: the decisions already taken are there. The name is enough; the id is optional.",
552
555
  "annotations": {
553
556
  "readOnlyHint": true,
554
557
  "destructiveHint": false,
@@ -587,7 +590,7 @@ export const CATALOG = [
587
590
  {
588
591
  "name": "list_attachments",
589
592
  "title": "Project attachments",
590
- "description": "List the project attachments of one dossier: the site reports, borehole logs, meeting minutes and data sheets the user uploaded. Returns for each its id, name, type, page count and extraction status. Read one with read_attachment before answering on the project; a piece is evidence like a clause and is cited the same way.",
593
+ "description": "List the project attachments of one dossier: the site reports, borehole logs, meeting minutes and data sheets the user uploaded. Returns for each its id, name, type, page count and extraction status. Use this when a question on the project may rest on what the office uploaded; read_attachment opens one, and a piece is cited like a clause.",
591
594
  "annotations": {
592
595
  "readOnlyHint": true,
593
596
  "destructiveHint": false,
@@ -664,7 +667,7 @@ export const CATALOG = [
664
667
  {
665
668
  "name": "list_templates",
666
669
  "title": "Dossier templates",
667
- "description": "List the checklists the organization wrote for its types of case: each is a set of questions with the clauses they rest on. When a new project matches one, apply it with apply_template instead of opening the questions one by one.",
670
+ "description": "List the checklists the organization wrote for its types of case: each is a set of questions with the clauses they rest on. Use this when a new project may match a type of case the office already wrote down; apply_template opens all its questions at once.",
668
671
  "annotations": {
669
672
  "readOnlyHint": true,
670
673
  "destructiveHint": false,
@@ -757,7 +760,7 @@ export const CATALOG = [
757
760
  {
758
761
  "name": "get_skill",
759
762
  "title": "Read a skill",
760
- "description": "Read one skill in full: Markdown with its front matter (name, description) and its steps, rules and output shape. Follow it for the task at hand. The organization version wins over the Stratta version of the same name, because it carries the office plan and wording. Returns null when no skill has that name.",
763
+ "description": "Read one skill in full: Markdown with its front matter (name, description) and its steps, rules and output shape. Use this when a task matches a skill from list_skills; the skill describes how the office does it. The organization version wins over the Stratta version of the same name, because it carries the office plan and wording. Returns null when no skill has that name.",
761
764
  "annotations": {
762
765
  "readOnlyHint": true,
763
766
  "destructiveHint": false,
@@ -829,7 +832,7 @@ export const CATALOG = [
829
832
  ],
830
833
  "params": {
831
834
  "dossierId": "Dossier id from list_dossiers, open_dossier or load_dossier.",
832
- "limit": "Lines per page, 1 to 200. Default 50.",
835
+ "limit": "Lines per page, 1 to 200. Default 15, the most recent first.",
833
836
  "cursor": "The nextCursor of the previous page."
834
837
  }
835
838
  },
@@ -1022,7 +1025,7 @@ export const CATALOG = [
1022
1025
  {
1023
1026
  "name": "ingest_status",
1024
1027
  "title": "Check ingestion status",
1025
- "description": "Check whether a norm (by code) already exists in the workspace, how many sections it has and its coverage score (how much of the norm actually made it in). Call it before creating a document, to avoid duplicates.",
1028
+ "description": "Check whether a norm (by code) already exists in the workspace, how many sections it has and its coverage score (how much of the norm actually made it in). Use this before creating a document, so a norm is not ingested twice.",
1026
1029
  "annotations": {
1027
1030
  "readOnlyHint": true,
1028
1031
  "destructiveHint": false,
@@ -1170,7 +1173,7 @@ export const CATALOG = [
1170
1173
  {
1171
1174
  "name": "ingest_normalize_cross_refs",
1172
1175
  "title": "Normalize cross-references",
1173
- "description": "Scan every section of a document and rebuild the cross-references to other norms found in its text. Idempotent; run it after the sections are in.",
1176
+ "description": "Scan every section of a document and add the cross-references to other norms found in its text that are not recorded yet; existing ones are kept. Idempotent; run it after the sections are in.",
1174
1177
  "annotations": {
1175
1178
  "readOnlyHint": false,
1176
1179
  "destructiveHint": false,
@@ -1,7 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  export declare const getMethodology: import("./define.js").ToolDef<{}>;
3
3
  export declare const whoami: import("./define.js").ToolDef<{}>;
4
- export declare const listNorms: import("./define.js").ToolDef<{}>;
4
+ export declare const listNorms: import("./define.js").ToolDef<{
5
+ query: z.ZodOptional<z.ZodString>;
6
+ limit: z.ZodOptional<z.ZodNumber>;
7
+ }>;
5
8
  export declare const getToc: import("./define.js").ToolDef<{
6
9
  code: z.ZodString;
7
10
  edition: z.ZodOptional<z.ZodString>;
@@ -42,6 +45,9 @@ export declare const getFigure: import("./define.js").ToolDef<{
42
45
  figureId: z.ZodString;
43
46
  }>;
44
47
  export declare const readTools: (import("./define.js").ToolDef<{}> | import("./define.js").ToolDef<{
48
+ query: z.ZodOptional<z.ZodString>;
49
+ limit: z.ZodOptional<z.ZodNumber>;
50
+ }> | import("./define.js").ToolDef<{
45
51
  code: z.ZodString;
46
52
  edition: z.ZodOptional<z.ZodString>;
47
53
  maxDepth: z.ZodOptional<z.ZodNumber>;
@@ -50,11 +50,24 @@ export const whoami = defineTool({
50
50
  });
51
51
  export const listNorms = defineTool({
52
52
  ...headOf('list_norms'),
53
- inputSchema: {},
54
- run: async (client) => ({
55
- norms: await client.action(api._mcp.listPublishedNorms, {
56
- apiKey: requireApiKey(),
57
- }),
53
+ inputSchema: {
54
+ query: z
55
+ .string()
56
+ .max(200)
57
+ .optional()
58
+ .describe(param('list_norms', 'query')),
59
+ limit: z
60
+ .number()
61
+ .int()
62
+ .min(1)
63
+ .max(500)
64
+ .optional()
65
+ .describe(param('list_norms', 'limit')),
66
+ },
67
+ run: (client, args) => client.action(api._mcp.listNorms, {
68
+ apiKey: requireApiKey(),
69
+ query: args.query,
70
+ limit: args.limit,
58
71
  }),
59
72
  });
60
73
  export const getToc = defineTool({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stratta/mcp",
3
3
  "mcpName": "ch.stratta/mcp",
4
- "version": "1.18.1",
4
+ "version": "1.20.0",
5
5
  "description": "MCP server exposing your organization’s norms and reference documents (standards, laws, regulations) to any MCP client, with exact citations, via Stratta TreeRAG.",
6
6
  "license": "UNLICENSED",
7
7
  "author": "SmartFlow <hello@stratta.ch>",
@@ -51,7 +51,9 @@ TOC_DOT_RE = re.compile(r"\.\s*\.\s*\.")
51
51
  CLEAN_DOTS = re.compile(r"\s*(\.\s*){3,}.*$")
52
52
 
53
53
  CAPTION_RE = re.compile(
54
- r"^\s*(?:Figure|Fig\.|Bild|Abbildung|Figura)\s+(\d+[a-z]?)\b[\s:.\-—–]*(.*)$",
54
+ # « Figure 1.2 » garde tout son numéro : réduit à « 1 », il était dédoublonné avec « 1.1 »
55
+ # et ASIRI ne gardait qu'une figure par chapitre.
56
+ r"^\s*(?:Figure|Fig\.|Bild|Abbildung|Figura)\s+(\d+(?:\.\d+)*[a-z]?)\b[\s:.\-—–]*(.*)$",
55
57
  re.IGNORECASE | re.MULTILINE,
56
58
  )
57
59
 
@@ -199,10 +201,26 @@ def page_lines(doc: "pymupdf.Document", pageno: int) -> list[str]:
199
201
  text = OCR_DOTTED_NUMBER_RE.sub(r"\1.\2", text)
200
202
  lines.append(text)
201
203
  lines = [l.rstrip() for l in lines]
204
+ # ⚠️⚠️ Regrouper les mots par hauteur suppose que la hauteur dit la ligne. Sur certains PDF
205
+ # (RMP, LVMP, SUVA BauAV neu : chaque mot posé à sa propre hauteur) le regroupement rend une
206
+ # soupe de mots que le compte de mots ne voit pas, puisqu'il ignore l'ordre. Quand les paires
207
+ # de mots consécutifs ne recoupent pas à 30 % celles de la lecture native, la native l'emporte.
208
+ native = page.get_text("text")
209
+ ours = _word_pairs("\n".join(lines))
210
+ if len(ours) >= 20 and len(ours & _word_pairs(native)) / len(ours) < 0.3:
211
+ lines = [l.rstrip() for l in join_split_headings(native).splitlines()]
202
212
  _LINES_CACHE[key] = lines
203
213
  return lines
204
214
 
205
215
 
216
+ _PAIR_WORD = re.compile(r"[0-9A-Za-zÀ-ÿ]{2,}")
217
+
218
+
219
+ def _word_pairs(text: str) -> set[tuple[str, str]]:
220
+ w = [x.lower() for x in _PAIR_WORD.findall(text)]
221
+ return set(zip(w, w[1:]))
222
+
223
+
206
224
  def page_text(doc: "pymupdf.Document", pageno: int) -> str:
207
225
  return "\n".join(page_lines(doc, pageno))
208
226
 
@@ -1091,7 +1109,48 @@ def extract_figures(
1091
1109
  "renderDpi": dpi,
1092
1110
  "mimeType": "image/png",
1093
1111
  }
1094
- return list(seen.values())
1112
+ # ⚠️ Une page qui porte un grand dessin sans légende « Figure N » (« Beilage C : », « Abb. 3 »,
1113
+ # un schéma sans titre) n'était pas retenue : 65 normes du corpus KF perdaient 847 images.
1114
+ # Une image de 300 × 300 px couvrant 10 % de la page au moins en fait une figure ; la page 1
1115
+ # (couverture) et les logos en sont exclus.
1116
+ have = {f["page"] for f in seen.values()}
1117
+ for pageno in range(1, doc.page_count):
1118
+ if pageno in toc_pages or pageno + 1 in have:
1119
+ continue
1120
+ page = doc[pageno]
1121
+ area = page.rect.width * page.rect.height
1122
+ big = any(
1123
+ i.get("width", 0) >= 300
1124
+ and i.get("height", 0) >= 300
1125
+ and (i["bbox"][2] - i["bbox"][0]) * (i["bbox"][3] - i["bbox"][1]) >= 0.10 * area
1126
+ for i in page.get_image_info()
1127
+ )
1128
+ if not big:
1129
+ continue
1130
+ m = FREE_LABEL_RE.search(page_text(doc, pageno))
1131
+ label = re.sub(r"\s+", " ", m.group(1)).strip().rstrip(":") if m else None
1132
+ rest = re.sub(r"\s+", " ", m.group(2)).strip() if m else ""
1133
+ number = label or f"p. {pageno + 1}"
1134
+ while number in seen:
1135
+ number = f"{number} (p. {pageno + 1})"
1136
+ pix = page.get_pixmap(matrix=matrix, alpha=False)
1137
+ page_png = figs_dir / f"page-{pageno + 1:03d}.png"
1138
+ pix.save(str(page_png))
1139
+ seen[number] = {
1140
+ "figureNumber": number,
1141
+ "caption": (f"{label} — {rest}" if label and rest else label or f"Illustration, page {pageno + 1}")[:240],
1142
+ "page": pageno + 1,
1143
+ "fileName": f"figures/{page_png.name}",
1144
+ "renderDpi": dpi,
1145
+ "mimeType": "image/png",
1146
+ }
1147
+ return sorted(seen.values(), key=lambda f: f["page"])
1148
+
1149
+
1150
+ FREE_LABEL_RE = re.compile(
1151
+ r"^\s*((?:Beilage|Anhang|Annexe|Abb\.?|Bild|Schéma|Schema|Planche|Tafel)\s*[\w.]*)\s*[:\-–]?\s*(.{3,120})$",
1152
+ re.IGNORECASE | re.MULTILINE,
1153
+ )
1095
1154
 
1096
1155
 
1097
1156
  def guess_language(doc: "pymupdf.Document") -> str:
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: consult-stratta
3
- description: Use Stratta to answer civil engineering questions from licensed SIA, Eurocode, or office norms with exact citations, cross-reference checks, figures, and optional project dossiers. Trigger phrases: "selon la norme", "d'apres la SIA", "que dit l'Eurocode", "verifie dans les normes", "reprends le dossier".
3
+ description: Use Stratta to answer professional questions from the norms and reference documents the organization deposited (technical standards such as SIA or Eurocodes, laws, ordinances, regulations, accounting standards, in-house references) with exact citations, cross-reference checks, figures, and optional project dossiers. Trigger phrases: "selon la norme", "que dit la loi", "d'apres la SIA", "que dit l'Eurocode", "verifie dans les normes", "reprends le dossier".
4
4
  ---
5
5
 
6
6
  # Consult Stratta
7
7
 
8
- Use this skill when the user asks a technical civil engineering question that should be answered from the norms available in Stratta, or when the user asks to resume, create or update a Stratta project dossier.
8
+ Use this skill when the user asks a professional question that should be answered from the norms, laws or reference documents available in Stratta, or when the user asks to resume, create or update a Stratta project dossier.
9
9
 
10
10
  You answer professionals (engineers, lawyers, accountants, public administrations and others) from the norms and reference documents their organization holds in Stratta (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only from them. The corpus below is the whole of what you can read: a document absent from it cannot be consulted, and a question outside it is answered by saying so, not by general knowledge. "Norm" below means any such document. Reply in the language the user writes in; quote the norm in the language it is written in and translate only when asked.
11
11