@a-company/paradigm 3.14.1 → 3.15.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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../paradigm-mcp/src/tools/reindex.ts
4
- import * as fs8 from "fs";
5
- import * as path9 from "path";
6
- import * as yaml7 from "js-yaml";
4
+ import * as fs9 from "fs";
5
+ import * as path10 from "path";
6
+ import * as yaml8 from "js-yaml";
7
7
 
8
8
  // ../paradigm-mcp/node_modules/.pnpm/@a-company+premise-core@0.2.0_typescript@5.9.3/node_modules/@a-company/premise-core/dist/index.js
9
9
  import * as yaml3 from "js-yaml";
@@ -1991,8 +1991,8 @@ var SessionTracker = class {
1991
1991
  * Extract resource type from URI
1992
1992
  */
1993
1993
  extractResourceType(uri) {
1994
- const path10 = uri.replace("paradigm://", "");
1995
- const firstPart = path10.split("/")[0];
1994
+ const path11 = uri.replace("paradigm://", "");
1995
+ const firstPart = path11.split("/")[0];
1996
1996
  return firstPart || "unknown";
1997
1997
  }
1998
1998
  /**
@@ -4389,8 +4389,8 @@ async function getAffectedPersonas(rootDir, symbol) {
4389
4389
  }
4390
4390
  return results;
4391
4391
  }
4392
- function deepGet(obj, path10) {
4393
- const parts = path10.split(/[.\[\]]+/).filter(Boolean);
4392
+ function deepGet(obj, path11) {
4393
+ const parts = path11.split(/[.\[\]]+/).filter(Boolean);
4394
4394
  let current = obj;
4395
4395
  for (const part of parts) {
4396
4396
  if (current == null || typeof current !== "object") return void 0;
@@ -4558,6 +4558,341 @@ async function validateAgainstSentinel(persona, options = {}) {
4558
4558
  };
4559
4559
  }
4560
4560
 
4561
+ // ../paradigm-mcp/src/utils/protocol-loader.ts
4562
+ import * as fs8 from "fs";
4563
+ import * as path9 from "path";
4564
+ import * as yaml7 from "js-yaml";
4565
+ var PROTOCOLS_DIR = ".paradigm/protocols";
4566
+ var INDEX_FILE2 = "index.yaml";
4567
+ async function loadProtocols(rootDir) {
4568
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4569
+ if (!fs8.existsSync(protocolsDir)) {
4570
+ return [];
4571
+ }
4572
+ const files = fs8.readdirSync(protocolsDir).filter((f) => f.endsWith(".protocol")).sort();
4573
+ const protocols = [];
4574
+ for (const file of files) {
4575
+ try {
4576
+ const content = fs8.readFileSync(path9.join(protocolsDir, file), "utf8");
4577
+ const protocol = yaml7.load(content);
4578
+ if (protocol?.id && protocol?.name) {
4579
+ protocols.push(protocol);
4580
+ }
4581
+ } catch {
4582
+ }
4583
+ }
4584
+ return protocols;
4585
+ }
4586
+ async function loadProtocol(rootDir, id) {
4587
+ const slug = id.replace(/^P-/, "");
4588
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4589
+ if (fs8.existsSync(filePath)) {
4590
+ try {
4591
+ const content = fs8.readFileSync(filePath, "utf8");
4592
+ return yaml7.load(content);
4593
+ } catch {
4594
+ return null;
4595
+ }
4596
+ }
4597
+ const protocols = await loadProtocols(rootDir);
4598
+ return protocols.find((p) => p.id === id) || null;
4599
+ }
4600
+ async function loadProtocolIndex(rootDir) {
4601
+ const indexPath = path9.join(rootDir, PROTOCOLS_DIR, INDEX_FILE2);
4602
+ if (!fs8.existsSync(indexPath)) {
4603
+ return null;
4604
+ }
4605
+ try {
4606
+ const content = fs8.readFileSync(indexPath, "utf8");
4607
+ return yaml7.load(content);
4608
+ } catch {
4609
+ return null;
4610
+ }
4611
+ }
4612
+ async function searchProtocols(rootDir, task, limit = 3) {
4613
+ const protocols = await loadProtocols(rootDir);
4614
+ if (protocols.length === 0) return [];
4615
+ const keywords = tokenize(task);
4616
+ if (keywords.length === 0) return [];
4617
+ const scored = [];
4618
+ for (const protocol of protocols) {
4619
+ let score = 0;
4620
+ for (const trigger of protocol.trigger) {
4621
+ const triggerLower = trigger.toLowerCase();
4622
+ for (const kw of keywords) {
4623
+ if (triggerLower.includes(kw)) {
4624
+ score += 3;
4625
+ }
4626
+ }
4627
+ }
4628
+ for (const tag of protocol.tags) {
4629
+ const tagLower = tag.toLowerCase();
4630
+ for (const kw of keywords) {
4631
+ if (tagLower.includes(kw) || kw.includes(tagLower)) {
4632
+ score += 2;
4633
+ }
4634
+ }
4635
+ }
4636
+ const nameLower = protocol.name.toLowerCase();
4637
+ const descLower = protocol.description.toLowerCase();
4638
+ for (const kw of keywords) {
4639
+ if (nameLower.includes(kw)) score += 1;
4640
+ if (descLower.includes(kw)) score += 1;
4641
+ }
4642
+ for (const step of protocol.steps) {
4643
+ if (step.notes) {
4644
+ const notesLower = step.notes.toLowerCase();
4645
+ for (const kw of keywords) {
4646
+ if (notesLower.includes(kw)) score += 0.5;
4647
+ }
4648
+ }
4649
+ }
4650
+ if (score > 0) {
4651
+ scored.push({ protocol, score });
4652
+ }
4653
+ }
4654
+ scored.sort((a, b) => b.score - a.score);
4655
+ return scored.slice(0, limit);
4656
+ }
4657
+ async function recordProtocol(rootDir, protocol) {
4658
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4659
+ if (!fs8.existsSync(protocolsDir)) {
4660
+ fs8.mkdirSync(protocolsDir, { recursive: true });
4661
+ }
4662
+ const slug = slugify(protocol.name);
4663
+ const id = `P-${slug}`;
4664
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4665
+ const full = {
4666
+ id,
4667
+ name: protocol.name,
4668
+ description: protocol.description,
4669
+ trigger: protocol.trigger,
4670
+ tags: protocol.tags,
4671
+ symbols: protocol.symbols || [],
4672
+ exemplar: protocol.exemplar,
4673
+ steps: protocol.steps,
4674
+ recorded_from: protocol.recorded_from,
4675
+ recorded_at: now,
4676
+ last_verified: now,
4677
+ verified_by: protocol.verified_by || "claude-opus-4-6",
4678
+ status: "current"
4679
+ };
4680
+ const filePath = path9.join(protocolsDir, `${slug}.protocol`);
4681
+ fs8.writeFileSync(filePath, yaml7.dump(full, { lineWidth: -1, noRefs: true }), "utf8");
4682
+ return id;
4683
+ }
4684
+ async function updateProtocol(rootDir, id, partial, refresh = false) {
4685
+ const protocol = await loadProtocol(rootDir, id);
4686
+ if (!protocol) return false;
4687
+ if (partial.name !== void 0) protocol.name = partial.name;
4688
+ if (partial.description !== void 0) protocol.description = partial.description;
4689
+ if (partial.trigger !== void 0) protocol.trigger = partial.trigger;
4690
+ if (partial.tags !== void 0) protocol.tags = partial.tags;
4691
+ if (partial.symbols !== void 0) protocol.symbols = partial.symbols;
4692
+ if (partial.exemplar !== void 0) protocol.exemplar = partial.exemplar;
4693
+ if (partial.steps !== void 0) protocol.steps = partial.steps;
4694
+ if (partial.status !== void 0) protocol.status = partial.status;
4695
+ if (partial.verified_by !== void 0) protocol.verified_by = partial.verified_by;
4696
+ if (refresh) {
4697
+ protocol.last_verified = (/* @__PURE__ */ new Date()).toISOString();
4698
+ protocol.verified_by = partial.verified_by || "claude-opus-4-6";
4699
+ }
4700
+ const slug = id.replace(/^P-/, "");
4701
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4702
+ fs8.writeFileSync(filePath, yaml7.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
4703
+ return true;
4704
+ }
4705
+ function validateProtocol(rootDir, protocol) {
4706
+ const issues = [];
4707
+ let status = "current";
4708
+ if (protocol.exemplar) {
4709
+ const exemplarPath = path9.join(rootDir, protocol.exemplar);
4710
+ if (!fs8.existsSync(exemplarPath)) {
4711
+ issues.push(`Exemplar missing: ${protocol.exemplar}`);
4712
+ status = "broken";
4713
+ } else {
4714
+ const stat = fs8.statSync(exemplarPath);
4715
+ if (stat.mtime.toISOString() > protocol.last_verified) {
4716
+ issues.push(`Exemplar modified since last verified: ${protocol.exemplar}`);
4717
+ if (status !== "broken") status = "stale";
4718
+ }
4719
+ }
4720
+ }
4721
+ for (const step of protocol.steps) {
4722
+ if (step.template_from) {
4723
+ const templatePath = path9.join(rootDir, step.template_from);
4724
+ if (!fs8.existsSync(templatePath)) {
4725
+ issues.push(`Template file missing: ${step.template_from}`);
4726
+ status = "broken";
4727
+ }
4728
+ }
4729
+ if (step.action === "modify" && step.target) {
4730
+ const targetPath = path9.join(rootDir, step.target);
4731
+ if (!step.target.includes("{") && !fs8.existsSync(targetPath)) {
4732
+ issues.push(`Modify target missing: ${step.target}`);
4733
+ status = "broken";
4734
+ }
4735
+ }
4736
+ }
4737
+ return { status, issues };
4738
+ }
4739
+ async function rebuildProtocolIndex(rootDir) {
4740
+ const protocols = await loadProtocols(rootDir);
4741
+ const entries = [];
4742
+ let current = 0;
4743
+ let stale = 0;
4744
+ let broken = 0;
4745
+ for (const protocol of protocols) {
4746
+ const validation = validateProtocol(rootDir, protocol);
4747
+ if (protocol.status !== validation.status) {
4748
+ protocol.status = validation.status;
4749
+ const slug = protocol.id.replace(/^P-/, "");
4750
+ const filePath = path9.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
4751
+ if (fs8.existsSync(filePath)) {
4752
+ fs8.writeFileSync(filePath, yaml7.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
4753
+ }
4754
+ }
4755
+ switch (validation.status) {
4756
+ case "current":
4757
+ current++;
4758
+ break;
4759
+ case "stale":
4760
+ stale++;
4761
+ break;
4762
+ case "broken":
4763
+ broken++;
4764
+ break;
4765
+ }
4766
+ entries.push({
4767
+ id: protocol.id,
4768
+ name: protocol.name,
4769
+ status: validation.status,
4770
+ last_verified: protocol.last_verified,
4771
+ trigger: protocol.trigger,
4772
+ tags: protocol.tags
4773
+ });
4774
+ }
4775
+ const index = {
4776
+ version: "1.0",
4777
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
4778
+ protocols: entries,
4779
+ health: {
4780
+ total: protocols.length,
4781
+ current,
4782
+ stale,
4783
+ broken
4784
+ }
4785
+ };
4786
+ const protocolsDir = path9.join(rootDir, PROTOCOLS_DIR);
4787
+ if (protocols.length > 0) {
4788
+ if (!fs8.existsSync(protocolsDir)) {
4789
+ fs8.mkdirSync(protocolsDir, { recursive: true });
4790
+ }
4791
+ const indexPath = path9.join(protocolsDir, INDEX_FILE2);
4792
+ fs8.writeFileSync(indexPath, yaml7.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
4793
+ }
4794
+ return index;
4795
+ }
4796
+ function detectProtocolSuggestion(rootDir, filesCreated, filesModified) {
4797
+ if (!filesCreated || filesCreated.length < 2) return null;
4798
+ const dirGroups = {};
4799
+ for (const file of filesCreated) {
4800
+ const dir = path9.dirname(file);
4801
+ if (!dirGroups[dir]) dirGroups[dir] = [];
4802
+ dirGroups[dir].push(file);
4803
+ }
4804
+ for (const [dir, created] of Object.entries(dirGroups)) {
4805
+ if (created.length < 2) continue;
4806
+ const absDir = path9.join(rootDir, dir);
4807
+ if (!fs8.existsSync(absDir)) continue;
4808
+ const existing = fs8.readdirSync(absDir).filter((f) => {
4809
+ const ext = path9.extname(f);
4810
+ return [".ts", ".tsx", ".js", ".jsx", ".rs", ".py"].includes(ext);
4811
+ });
4812
+ const preExisting = existing.filter((f) => !created.some((c) => path9.basename(c) === f));
4813
+ if (preExisting.length > 0) {
4814
+ const exemplar = path9.join(dir, preExisting[0]);
4815
+ const steps = [
4816
+ ...created.map((f) => ({ action: "create", target: f })),
4817
+ ...filesModified.map((f) => ({ action: "modify", target: f }))
4818
+ ];
4819
+ return {
4820
+ hint: `This session created ${created.length} new files in ${dir}/ following existing patterns. Consider recording a protocol.`,
4821
+ draft: {
4822
+ name: `Add a ${path9.basename(dir).replace(/s$/, "")}`,
4823
+ exemplar,
4824
+ steps
4825
+ }
4826
+ };
4827
+ }
4828
+ }
4829
+ return null;
4830
+ }
4831
+ function tokenize(text) {
4832
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 1).filter((w) => !STOP_WORDS.has(w));
4833
+ }
4834
+ var STOP_WORDS = /* @__PURE__ */ new Set([
4835
+ "a",
4836
+ "an",
4837
+ "the",
4838
+ "is",
4839
+ "are",
4840
+ "was",
4841
+ "were",
4842
+ "be",
4843
+ "been",
4844
+ "to",
4845
+ "of",
4846
+ "in",
4847
+ "for",
4848
+ "on",
4849
+ "with",
4850
+ "at",
4851
+ "by",
4852
+ "from",
4853
+ "it",
4854
+ "this",
4855
+ "that",
4856
+ "and",
4857
+ "or",
4858
+ "but",
4859
+ "if",
4860
+ "then",
4861
+ "so",
4862
+ "as",
4863
+ "do",
4864
+ "does",
4865
+ "did",
4866
+ "will",
4867
+ "would",
4868
+ "can",
4869
+ "could",
4870
+ "should",
4871
+ "may",
4872
+ "might",
4873
+ "must",
4874
+ "shall",
4875
+ "i",
4876
+ "me",
4877
+ "my",
4878
+ "we",
4879
+ "our",
4880
+ "you",
4881
+ "your",
4882
+ "he",
4883
+ "she",
4884
+ "how",
4885
+ "what",
4886
+ "when",
4887
+ "where",
4888
+ "which",
4889
+ "who",
4890
+ "whom"
4891
+ ]);
4892
+ function slugify(name) {
4893
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4894
+ }
4895
+
4561
4896
  // ../paradigm-mcp/src/tools/reindex.ts
4562
4897
  var SYMBOL_CATEGORIES = {
4563
4898
  "@": { category: "features", prefix: "@" },
@@ -4646,10 +4981,10 @@ async function rebuildStaticFiles(rootDir, ctx) {
4646
4981
  } else {
4647
4982
  aggregation = await aggregateFromDirectory(rootDir);
4648
4983
  }
4649
- const projectName = ctx?.projectName || path9.basename(rootDir);
4650
- const paradigmDir = path9.join(rootDir, ".paradigm");
4651
- if (!fs8.existsSync(paradigmDir)) {
4652
- fs8.mkdirSync(paradigmDir, { recursive: true });
4984
+ const projectName = ctx?.projectName || path10.basename(rootDir);
4985
+ const paradigmDir = path10.join(rootDir, ".paradigm");
4986
+ if (!fs9.existsSync(paradigmDir)) {
4987
+ fs9.mkdirSync(paradigmDir, { recursive: true });
4653
4988
  }
4654
4989
  const scanIndex = generateScanIndex(
4655
4990
  {
@@ -4659,22 +4994,22 @@ async function rebuildStaticFiles(rootDir, ctx) {
4659
4994
  },
4660
4995
  { projectName }
4661
4996
  );
4662
- const scanIndexPath = path9.join(paradigmDir, "scan-index.json");
4663
- fs8.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
4997
+ const scanIndexPath = path10.join(paradigmDir, "scan-index.json");
4998
+ fs9.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
4664
4999
  filesWritten.push(".paradigm/scan-index.json");
4665
5000
  const navigatorData = buildNavigatorData(rootDir, aggregation);
4666
- const navigatorPath = path9.join(paradigmDir, "navigator.yaml");
4667
- fs8.writeFileSync(
5001
+ const navigatorPath = path10.join(paradigmDir, "navigator.yaml");
5002
+ fs9.writeFileSync(
4668
5003
  navigatorPath,
4669
- yaml7.dump(navigatorData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }),
5004
+ yaml8.dump(navigatorData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }),
4670
5005
  "utf8"
4671
5006
  );
4672
5007
  filesWritten.push(".paradigm/navigator.yaml");
4673
5008
  const flowIndex = generateFlowIndex(rootDir, aggregation.purposeFiles);
4674
5009
  let flowCount = 0;
4675
5010
  if (flowIndex && Object.keys(flowIndex.flows).length > 0) {
4676
- const flowIndexPath = path9.join(paradigmDir, "flow-index.json");
4677
- fs8.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
5011
+ const flowIndexPath = path10.join(paradigmDir, "flow-index.json");
5012
+ fs9.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
4678
5013
  filesWritten.push(".paradigm/flow-index.json");
4679
5014
  flowCount = Object.keys(flowIndex.flows).length;
4680
5015
  }
@@ -4706,6 +5041,15 @@ async function rebuildStaticFiles(rootDir, ctx) {
4706
5041
  }
4707
5042
  } catch {
4708
5043
  }
5044
+ let protocolHealth;
5045
+ try {
5046
+ const protocolIndex = await rebuildProtocolIndex(rootDir);
5047
+ if (protocolIndex.health.total > 0) {
5048
+ protocolHealth = protocolIndex.health;
5049
+ filesWritten.push(".paradigm/protocols/index.yaml");
5050
+ }
5051
+ } catch {
5052
+ }
4709
5053
  const breakdown = {};
4710
5054
  for (const sym of aggregation.symbols) {
4711
5055
  breakdown[sym.type] = (breakdown[sym.type] || 0) + 1;
@@ -4717,7 +5061,8 @@ async function rebuildStaticFiles(rootDir, ctx) {
4717
5061
  breakdown,
4718
5062
  flowCount,
4719
5063
  aspectGraphStats,
4720
- personaCount
5064
+ personaCount,
5065
+ protocolHealth
4721
5066
  };
4722
5067
  }
4723
5068
  function buildNavigatorData(rootDir, aggregation) {
@@ -4733,7 +5078,7 @@ function buildNavigatorData(rootDir, aggregation) {
4733
5078
  function buildStructure(rootDir) {
4734
5079
  const structure = {};
4735
5080
  for (const [category, patterns] of Object.entries(DIRECTORY_PATTERNS)) {
4736
- const existingPaths = patterns.filter((p) => fs8.existsSync(path9.join(rootDir, p)));
5081
+ const existingPaths = patterns.filter((p) => fs9.existsSync(path10.join(rootDir, p)));
4737
5082
  if (existingPaths.length > 0) {
4738
5083
  const symbolInfo = Object.values(SYMBOL_CATEGORIES).find((s) => s.category === category);
4739
5084
  structure[category] = { paths: existingPaths, symbol: symbolInfo?.prefix || "@" };
@@ -4744,7 +5089,7 @@ function buildStructure(rootDir) {
4744
5089
  function buildKeyFiles(rootDir) {
4745
5090
  const keyFiles = {};
4746
5091
  for (const [category, patterns] of Object.entries(KEY_FILE_PATTERNS)) {
4747
- const existingPaths = patterns.filter((p) => fs8.existsSync(path9.join(rootDir, p)));
5092
+ const existingPaths = patterns.filter((p) => fs9.existsSync(path10.join(rootDir, p)));
4748
5093
  if (existingPaths.length > 0) {
4749
5094
  keyFiles[category] = existingPaths;
4750
5095
  }
@@ -4760,10 +5105,10 @@ function buildSkipPatterns(rootDir) {
4760
5105
  unless_testing: [...DEFAULT_SKIP_PATTERNS.unless_testing],
4761
5106
  unless_docs: [...DEFAULT_SKIP_PATTERNS.unless_docs]
4762
5107
  };
4763
- const gitignorePath = path9.join(rootDir, ".gitignore");
4764
- if (fs8.existsSync(gitignorePath)) {
5108
+ const gitignorePath = path10.join(rootDir, ".gitignore");
5109
+ if (fs9.existsSync(gitignorePath)) {
4765
5110
  try {
4766
- const content = fs8.readFileSync(gitignorePath, "utf8");
5111
+ const content = fs9.readFileSync(gitignorePath, "utf8");
4767
5112
  const gitignorePatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter(
4768
5113
  (line) => line.endsWith("/") || line.includes("*") || ["node_modules", "dist", "build", ".cache"].some((p) => line.includes(p))
4769
5114
  ).slice(0, 20);
@@ -4812,11 +5157,11 @@ function buildSymbolMap(symbols, purposeFiles, _rootDir) {
4812
5157
  symbolMap[symbolId] = symbol.filePath;
4813
5158
  } else {
4814
5159
  const matchingPurpose = purposeFiles.find((pf) => {
4815
- const dir = path9.dirname(pf);
5160
+ const dir = path10.dirname(pf);
4816
5161
  return dir.toLowerCase().includes(symbol.id.toLowerCase());
4817
5162
  });
4818
5163
  if (matchingPurpose) {
4819
- symbolMap[symbolId] = path9.dirname(matchingPurpose) + "/";
5164
+ symbolMap[symbolId] = path10.dirname(matchingPurpose) + "/";
4820
5165
  }
4821
5166
  }
4822
5167
  }
@@ -4827,8 +5172,8 @@ function generateFlowIndex(rootDir, purposeFiles) {
4827
5172
  const symbolToFlows = {};
4828
5173
  for (const filePath of purposeFiles) {
4829
5174
  try {
4830
- const content = fs8.readFileSync(filePath, "utf8");
4831
- const data = yaml7.load(content);
5175
+ const content = fs9.readFileSync(filePath, "utf8");
5176
+ const data = yaml8.load(content);
4832
5177
  if (!data?.flows) continue;
4833
5178
  if (Array.isArray(data.flows)) {
4834
5179
  for (const flowItem of data.flows) {
@@ -4841,7 +5186,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
4841
5186
  id: flowId,
4842
5187
  description: flow.description || "",
4843
5188
  steps,
4844
- definedIn: path9.relative(rootDir, filePath)
5189
+ definedIn: path10.relative(rootDir, filePath)
4845
5190
  };
4846
5191
  indexFlowSymbols(flowId, steps, symbolToFlows);
4847
5192
  }
@@ -4857,7 +5202,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
4857
5202
  trigger: flowDef.trigger,
4858
5203
  steps,
4859
5204
  validation: flowDef.validation,
4860
- definedIn: path9.relative(rootDir, filePath)
5205
+ definedIn: path10.relative(rootDir, filePath)
4861
5206
  };
4862
5207
  indexFlowSymbols(flowId, steps, symbolToFlows);
4863
5208
  }
@@ -4965,6 +5310,14 @@ export {
4965
5310
  getPersonaCoverage,
4966
5311
  getAffectedPersonas,
4967
5312
  validateAgainstSentinel,
5313
+ loadProtocols,
5314
+ loadProtocol,
5315
+ loadProtocolIndex,
5316
+ searchProtocols,
5317
+ recordProtocol,
5318
+ updateProtocol,
5319
+ validateProtocol,
5320
+ detectProtocolSuggestion,
4968
5321
  getReindexToolsList,
4969
5322
  handleReindexTool,
4970
5323
  rebuildStaticFiles
package/dist/mcp.js CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  createPersona,
37
37
  deleteLoreEntry,
38
38
  deletePersona,
39
+ detectProtocolSuggestion,
39
40
  findPurposeFiles,
40
41
  getAffectedPersonas,
41
42
  getAllEdgesFor,
@@ -66,6 +67,9 @@ import {
66
67
  loadLoreTimeline,
67
68
  loadPersona,
68
69
  loadPersonas,
70
+ loadProtocol,
71
+ loadProtocolIndex,
72
+ loadProtocols,
69
73
  openAspectGraph,
70
74
  parseGateConfig,
71
75
  parsePurposeFileDetailed,
@@ -73,7 +77,9 @@ import {
73
77
  recordGlobalAntipattern,
74
78
  recordGlobalDecision,
75
79
  recordLoreEntry,
80
+ recordProtocol,
76
81
  removeStep,
82
+ searchProtocols,
77
83
  searchSymbols,
78
84
  serializePurposeFile,
79
85
  toolCache,
@@ -81,10 +87,12 @@ import {
81
87
  trackToolCall,
82
88
  updateLoreEntry,
83
89
  updatePersona,
90
+ updateProtocol,
84
91
  validateAgainstSentinel,
85
92
  validatePersona,
93
+ validateProtocol,
86
94
  validatePurposeFile
87
- } from "./chunk-O5ZO5LSW.js";
95
+ } from "./chunk-D4VBBKGV.js";
88
96
  import {
89
97
  getPluginUpdateNotice,
90
98
  schedulePluginUpdateCheck
@@ -8574,6 +8582,17 @@ async function handleLoreTool(name, args, ctx) {
8574
8582
  };
8575
8583
  const id = await recordLoreEntry(ctx.rootDir, entry);
8576
8584
  getSessionTracker().setLastLoreEntryId(id);
8585
+ let protocol_suggestion = null;
8586
+ try {
8587
+ if (files_created && files_created.length >= 2) {
8588
+ protocol_suggestion = detectProtocolSuggestion(
8589
+ ctx.rootDir,
8590
+ files_created,
8591
+ files_modified || []
8592
+ );
8593
+ }
8594
+ } catch {
8595
+ }
8577
8596
  return {
8578
8597
  handled: true,
8579
8598
  text: JSON.stringify({
@@ -8581,7 +8600,8 @@ async function handleLoreTool(name, args, ctx) {
8581
8600
  id,
8582
8601
  type,
8583
8602
  title,
8584
- message: "Lore entry recorded successfully"
8603
+ message: "Lore entry recorded successfully",
8604
+ ...protocol_suggestion ? { protocol_suggestion } : {}
8585
8605
  })
8586
8606
  };
8587
8607
  }
@@ -11553,6 +11573,364 @@ async function handlePersonaTool(name, args, ctx) {
11553
11573
  }
11554
11574
  }
11555
11575
 
11576
+ // ../paradigm-mcp/src/tools/protocols.ts
11577
+ function getProtocolsToolsList() {
11578
+ return [
11579
+ {
11580
+ name: "paradigm_protocol_search",
11581
+ description: "Search for protocols matching a task description. Call BEFORE exploring the codebase \u2014 if a matching protocol exists, follow its steps instead of discovering the pattern from scratch. Returns top matches with steps, exemplar, and freshness info. ~200 tokens.",
11582
+ inputSchema: {
11583
+ type: "object",
11584
+ properties: {
11585
+ task: {
11586
+ type: "string",
11587
+ description: 'Task description to search for (e.g., "add a new page", "add API route")'
11588
+ },
11589
+ limit: {
11590
+ type: "number",
11591
+ description: "Maximum results (default: 3)"
11592
+ }
11593
+ },
11594
+ required: ["task"]
11595
+ },
11596
+ annotations: {
11597
+ readOnlyHint: true,
11598
+ destructiveHint: false
11599
+ }
11600
+ },
11601
+ {
11602
+ name: "paradigm_protocol_get",
11603
+ description: "Get a specific protocol by ID with full details and freshness check. ~300 tokens.",
11604
+ inputSchema: {
11605
+ type: "object",
11606
+ properties: {
11607
+ id: {
11608
+ type: "string",
11609
+ description: 'Protocol ID (e.g., "P-add-view")'
11610
+ }
11611
+ },
11612
+ required: ["id"]
11613
+ },
11614
+ annotations: {
11615
+ readOnlyHint: true,
11616
+ destructiveHint: false
11617
+ }
11618
+ },
11619
+ {
11620
+ name: "paradigm_protocol_record",
11621
+ description: "Record a new protocol after completing repeatable work. Captures the steps you followed so future agents can skip exploration. ~100 tokens.",
11622
+ inputSchema: {
11623
+ type: "object",
11624
+ properties: {
11625
+ name: {
11626
+ type: "string",
11627
+ description: 'Protocol name (e.g., "Add a new view")'
11628
+ },
11629
+ description: {
11630
+ type: "string",
11631
+ description: "What this protocol accomplishes"
11632
+ },
11633
+ trigger: {
11634
+ type: "array",
11635
+ items: { type: "string" },
11636
+ description: 'Phrases that should match this protocol (e.g., ["add view", "new page"])'
11637
+ },
11638
+ tags: {
11639
+ type: "array",
11640
+ items: { type: "string" },
11641
+ description: 'Classification tags (e.g., ["ui", "frontend"])'
11642
+ },
11643
+ symbols: {
11644
+ type: "array",
11645
+ items: { type: "string" },
11646
+ description: 'Paradigm symbols involved (e.g., ["#logs-view"])'
11647
+ },
11648
+ exemplar: {
11649
+ type: "string",
11650
+ description: 'Canonical file to study for this pattern (e.g., "ui/src/views/LogsView.tsx")'
11651
+ },
11652
+ steps: {
11653
+ type: "array",
11654
+ items: {
11655
+ type: "object",
11656
+ properties: {
11657
+ action: {
11658
+ type: "string",
11659
+ enum: ["create", "modify", "run", "verify"],
11660
+ description: "Step action type"
11661
+ },
11662
+ target: {
11663
+ type: "string",
11664
+ description: "File path (supports {Name}/{name} placeholders)"
11665
+ },
11666
+ template_from: {
11667
+ type: "string",
11668
+ description: "File to use as template (for create actions)"
11669
+ },
11670
+ reference: {
11671
+ type: "string",
11672
+ description: "Where in the file to make changes (for modify actions)"
11673
+ },
11674
+ command: {
11675
+ type: "string",
11676
+ description: "Command to execute (for run actions)"
11677
+ },
11678
+ notes: {
11679
+ type: "string",
11680
+ description: "Additional guidance for this step"
11681
+ }
11682
+ },
11683
+ required: ["action"]
11684
+ },
11685
+ description: "Ordered steps to follow"
11686
+ },
11687
+ recorded_from: {
11688
+ type: "string",
11689
+ description: 'Lore entry ID this protocol was learned from (e.g., "L-2026-03-01-001")'
11690
+ }
11691
+ },
11692
+ required: ["name", "description", "trigger", "tags", "steps"]
11693
+ },
11694
+ annotations: {
11695
+ readOnlyHint: false,
11696
+ destructiveHint: false
11697
+ }
11698
+ },
11699
+ {
11700
+ name: "paradigm_protocol_update",
11701
+ description: "Update an existing protocol. Use refresh:true after successfully following a protocol to bump last_verified. ~100 tokens.",
11702
+ inputSchema: {
11703
+ type: "object",
11704
+ properties: {
11705
+ id: {
11706
+ type: "string",
11707
+ description: 'Protocol ID to update (e.g., "P-add-view")'
11708
+ },
11709
+ refresh: {
11710
+ type: "boolean",
11711
+ description: "Set true to bump last_verified to now (use after successfully following the protocol)"
11712
+ },
11713
+ name: { type: "string", description: "Updated name" },
11714
+ description: { type: "string", description: "Updated description" },
11715
+ trigger: {
11716
+ type: "array",
11717
+ items: { type: "string" },
11718
+ description: "Updated trigger phrases"
11719
+ },
11720
+ tags: {
11721
+ type: "array",
11722
+ items: { type: "string" },
11723
+ description: "Updated tags"
11724
+ },
11725
+ symbols: {
11726
+ type: "array",
11727
+ items: { type: "string" },
11728
+ description: "Updated symbols"
11729
+ },
11730
+ exemplar: { type: "string", description: "Updated exemplar path" },
11731
+ steps: {
11732
+ type: "array",
11733
+ items: {
11734
+ type: "object",
11735
+ properties: {
11736
+ action: { type: "string", enum: ["create", "modify", "run", "verify"] },
11737
+ target: { type: "string" },
11738
+ template_from: { type: "string" },
11739
+ reference: { type: "string" },
11740
+ command: { type: "string" },
11741
+ notes: { type: "string" }
11742
+ },
11743
+ required: ["action"]
11744
+ },
11745
+ description: "Updated steps"
11746
+ }
11747
+ },
11748
+ required: ["id"]
11749
+ },
11750
+ annotations: {
11751
+ readOnlyHint: false,
11752
+ destructiveHint: false
11753
+ }
11754
+ },
11755
+ {
11756
+ name: "paradigm_protocol_validate",
11757
+ description: "Validate protocol references \u2014 check that referenced files exist, exemplars haven't drifted. Validates all protocols if no ID given. ~200 tokens.",
11758
+ inputSchema: {
11759
+ type: "object",
11760
+ properties: {
11761
+ id: {
11762
+ type: "string",
11763
+ description: "Protocol ID to validate (omit to validate all)"
11764
+ }
11765
+ }
11766
+ },
11767
+ annotations: {
11768
+ readOnlyHint: true,
11769
+ destructiveHint: false
11770
+ }
11771
+ }
11772
+ ];
11773
+ }
11774
+ async function handleProtocolsTool(name, args, ctx) {
11775
+ switch (name) {
11776
+ case "paradigm_protocol_search": {
11777
+ const task = args.task;
11778
+ const limit = args.limit || 3;
11779
+ const results = await searchProtocols(ctx.rootDir, task, limit);
11780
+ if (results.length === 0) {
11781
+ return {
11782
+ handled: true,
11783
+ text: JSON.stringify({
11784
+ count: 0,
11785
+ task,
11786
+ message: "No matching protocol found. Consider recording one after completing this task."
11787
+ })
11788
+ };
11789
+ }
11790
+ return {
11791
+ handled: true,
11792
+ text: JSON.stringify({
11793
+ count: results.length,
11794
+ task,
11795
+ matches: results.map((r) => ({
11796
+ id: r.protocol.id,
11797
+ name: r.protocol.name,
11798
+ description: r.protocol.description,
11799
+ score: r.score,
11800
+ status: r.protocol.status,
11801
+ exemplar: r.protocol.exemplar,
11802
+ last_verified: r.protocol.last_verified,
11803
+ steps: r.protocol.steps.map(summarizeStep)
11804
+ }))
11805
+ }, null, 2)
11806
+ };
11807
+ }
11808
+ case "paradigm_protocol_get": {
11809
+ const id = args.id;
11810
+ const protocol = await loadProtocol(ctx.rootDir, id);
11811
+ if (!protocol) {
11812
+ return {
11813
+ handled: true,
11814
+ text: JSON.stringify({ error: `Protocol not found: ${id}` })
11815
+ };
11816
+ }
11817
+ const validation = validateProtocol(ctx.rootDir, protocol);
11818
+ return {
11819
+ handled: true,
11820
+ text: JSON.stringify({
11821
+ ...protocol,
11822
+ freshness: {
11823
+ status: validation.status,
11824
+ issues: validation.issues
11825
+ }
11826
+ }, null, 2)
11827
+ };
11828
+ }
11829
+ case "paradigm_protocol_record": {
11830
+ const steps = args.steps || [];
11831
+ const id = await recordProtocol(ctx.rootDir, {
11832
+ name: args.name,
11833
+ description: args.description,
11834
+ trigger: args.trigger || [],
11835
+ tags: args.tags || [],
11836
+ symbols: args.symbols || [],
11837
+ exemplar: args.exemplar,
11838
+ steps,
11839
+ recorded_from: args.recorded_from,
11840
+ verified_by: "claude-opus-4-6"
11841
+ });
11842
+ return {
11843
+ handled: true,
11844
+ text: JSON.stringify({
11845
+ success: true,
11846
+ id,
11847
+ name: args.name,
11848
+ message: "Protocol recorded successfully"
11849
+ })
11850
+ };
11851
+ }
11852
+ case "paradigm_protocol_update": {
11853
+ const id = args.id;
11854
+ const refresh = args.refresh;
11855
+ const partial = {};
11856
+ if (args.name !== void 0) partial.name = args.name;
11857
+ if (args.description !== void 0) partial.description = args.description;
11858
+ if (args.trigger !== void 0) partial.trigger = args.trigger;
11859
+ if (args.tags !== void 0) partial.tags = args.tags;
11860
+ if (args.symbols !== void 0) partial.symbols = args.symbols;
11861
+ if (args.exemplar !== void 0) partial.exemplar = args.exemplar;
11862
+ if (args.steps !== void 0) partial.steps = args.steps;
11863
+ const success = await updateProtocol(ctx.rootDir, id, partial, refresh === true);
11864
+ return {
11865
+ handled: true,
11866
+ text: JSON.stringify({
11867
+ success,
11868
+ id,
11869
+ refreshed: refresh === true,
11870
+ message: success ? refresh ? "Protocol updated and verified" : "Protocol updated" : `Protocol not found: ${id}`
11871
+ })
11872
+ };
11873
+ }
11874
+ case "paradigm_protocol_validate": {
11875
+ const id = args.id;
11876
+ if (id) {
11877
+ const protocol = await loadProtocol(ctx.rootDir, id);
11878
+ if (!protocol) {
11879
+ return {
11880
+ handled: true,
11881
+ text: JSON.stringify({ error: `Protocol not found: ${id}` })
11882
+ };
11883
+ }
11884
+ const result = validateProtocol(ctx.rootDir, protocol);
11885
+ return {
11886
+ handled: true,
11887
+ text: JSON.stringify({
11888
+ id: protocol.id,
11889
+ name: protocol.name,
11890
+ status: result.status,
11891
+ issues: result.issues,
11892
+ last_verified: protocol.last_verified
11893
+ }, null, 2)
11894
+ };
11895
+ }
11896
+ const protocols = await loadProtocols(ctx.rootDir);
11897
+ const results = protocols.map((p) => {
11898
+ const v = validateProtocol(ctx.rootDir, p);
11899
+ return {
11900
+ id: p.id,
11901
+ name: p.name,
11902
+ status: v.status,
11903
+ issues: v.issues,
11904
+ last_verified: p.last_verified
11905
+ };
11906
+ });
11907
+ const health = {
11908
+ total: results.length,
11909
+ current: results.filter((r) => r.status === "current").length,
11910
+ stale: results.filter((r) => r.status === "stale").length,
11911
+ broken: results.filter((r) => r.status === "broken").length
11912
+ };
11913
+ return {
11914
+ handled: true,
11915
+ text: JSON.stringify({ protocols: results, health }, null, 2)
11916
+ };
11917
+ }
11918
+ default:
11919
+ return { handled: false, text: "" };
11920
+ }
11921
+ }
11922
+ function summarizeStep(step) {
11923
+ const result = {
11924
+ action: step.action
11925
+ };
11926
+ if (step.target) result.target = step.target;
11927
+ if (step.template_from) result.template_from = step.template_from;
11928
+ if (step.reference) result.reference = step.reference;
11929
+ if (step.command) result.command = step.command;
11930
+ if (step.notes) result.notes = step.notes;
11931
+ return result;
11932
+ }
11933
+
11556
11934
  // ../paradigm-mcp/src/tools/fallback-grep.ts
11557
11935
  import * as path23 from "path";
11558
11936
  import { execSync as execSync3 } from "child_process";
@@ -11844,6 +12222,8 @@ function registerTools(server, getContext2, reloadContext2) {
11844
12222
  // Assessment loop tools
11845
12223
  ...getAssessmentToolsList(),
11846
12224
  ...getPersonaToolsList(),
12225
+ // Protocol tools
12226
+ ...getProtocolsToolsList(),
11847
12227
  // Plugin update check
11848
12228
  {
11849
12229
  name: "paradigm_plugin_check",
@@ -12226,7 +12606,7 @@ function registerTools(server, getContext2, reloadContext2) {
12226
12606
  };
12227
12607
  }
12228
12608
  case "paradigm_status": {
12229
- const text = await toolCache.getOrCompute("status", () => {
12609
+ const text = await toolCache.getOrCompute("status", async () => {
12230
12610
  const counts = getSymbolCounts(ctx.index);
12231
12611
  const total = Object.values(counts).reduce((a, b) => a + b, 0);
12232
12612
  const examples = {};
@@ -12237,6 +12617,14 @@ function registerTools(server, getContext2, reloadContext2) {
12237
12617
  const platform2 = os.platform();
12238
12618
  const isWindows = platform2 === "win32";
12239
12619
  const shell = isWindows ? "PowerShell/CMD" : platform2 === "darwin" ? "zsh/bash" : "bash";
12620
+ let protocols;
12621
+ try {
12622
+ const protocolIndex = await loadProtocolIndex(ctx.rootDir);
12623
+ if (protocolIndex && protocolIndex.health.total > 0) {
12624
+ protocols = protocolIndex.health;
12625
+ }
12626
+ } catch {
12627
+ }
12240
12628
  return JSON.stringify({
12241
12629
  project: ctx.projectName,
12242
12630
  symbolSystem: "v2",
@@ -12251,6 +12639,7 @@ function registerTools(server, getContext2, reloadContext2) {
12251
12639
  examples,
12252
12640
  hasPortalYaml: ctx.gateConfig !== null,
12253
12641
  purposeFiles: ctx.aggregation.purposeFiles.length,
12642
+ ...protocols ? { protocols } : {},
12254
12643
  note: "Symbol System v2: Use tags [feature], [state], [integration], [idea] for classification",
12255
12644
  environment: {
12256
12645
  os: platform2,
@@ -12465,7 +12854,7 @@ Update command:
12465
12854
  trackToolCall(noWsText.length, name);
12466
12855
  return { content: [{ type: "text", text: noWsText }] };
12467
12856
  }
12468
- const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-4OOME3TT.js");
12857
+ const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-T4N3NG73.js");
12469
12858
  const memberResults = [];
12470
12859
  for (const member of ctx.workspace.config.members) {
12471
12860
  const memberAbsPath = path24.resolve(path24.dirname(ctx.workspace.workspacePath), member.path);
@@ -12648,6 +13037,15 @@ Update command:
12648
13037
  };
12649
13038
  }
12650
13039
  }
13040
+ if (name.startsWith("paradigm_protocol_")) {
13041
+ const result = await handleProtocolsTool(name, args, ctx);
13042
+ if (result.handled) {
13043
+ trackToolCall(result.text.length, name);
13044
+ return {
13045
+ content: [{ type: "text", text: result.text }]
13046
+ };
13047
+ }
13048
+ }
12651
13049
  if (name === "paradigm_reindex") {
12652
13050
  const reload = reloadContext2 || (async () => {
12653
13051
  });
@@ -3,7 +3,7 @@ import {
3
3
  getReindexToolsList,
4
4
  handleReindexTool,
5
5
  rebuildStaticFiles
6
- } from "./chunk-O5ZO5LSW.js";
6
+ } from "./chunk-D4VBBKGV.js";
7
7
  export {
8
8
  getReindexToolsList,
9
9
  handleReindexTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-company/paradigm",
3
- "version": "3.14.1",
3
+ "version": "3.15.0",
4
4
  "description": "Unified CLI for Paradigm developer tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",