runbrief 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +478 -65
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,13 +13,13 @@ name used in the install command.
13
13
  Claude Code:
14
14
 
15
15
  ```sh
16
- claude mcp add --transport stdio --scope user brief -- npx -y runbrief@0.1.0
16
+ claude mcp add --transport stdio --scope user brief -- npx -y runbrief@0.1.1
17
17
  ```
18
18
 
19
19
  Codex:
20
20
 
21
21
  ```sh
22
- codex mcp add brief -- npx -y runbrief@0.1.0
22
+ codex mcp add brief -- npx -y runbrief@0.1.1
23
23
  ```
24
24
 
25
25
  Generic MCP configuration:
@@ -29,7 +29,7 @@ Generic MCP configuration:
29
29
  "mcpServers": {
30
30
  "brief": {
31
31
  "command": "npx",
32
- "args": ["-y", "runbrief@0.1.0"]
32
+ "args": ["-y", "runbrief@0.1.1"]
33
33
  }
34
34
  }
35
35
  }
package/dist/cli.js CHANGED
@@ -636,6 +636,13 @@ var init_dist = __esm({
636
636
  sources: z.array(z.string()),
637
637
  tokenEstimate: z.number().int().nonnegative(),
638
638
  omitted: z.array(z.string()),
639
+ valueSignals: z.object({
640
+ relevantSourceCount: z.number().int().nonnegative(),
641
+ reusablePrimitiveCount: z.number().int().nonnegative(),
642
+ parserBackedEntityCount: z.number().int().nonnegative(),
643
+ appliedTeamGuidanceCount: z.number().int().nonnegative(),
644
+ suggestedTestCount: z.number().int().nonnegative()
645
+ }).optional(),
639
646
  teamGuidance: teamGuidanceDeliverySchema.optional()
640
647
  });
641
648
  contextReceiptRequestSchema = z.object({
@@ -2732,6 +2739,7 @@ function saveLocalContextReceiptUnlocked(repoRoot, packet, input) {
2732
2739
  mapNodeCount: packet.map.length,
2733
2740
  memoryCount: packet.memories.length,
2734
2741
  suggestedTests: packet.suggestedTests,
2742
+ ...packet.valueSignals ? { valueSignals: packet.valueSignals } : {},
2735
2743
  ...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {},
2736
2744
  git
2737
2745
  };
@@ -2830,6 +2838,18 @@ function buildLocalValueReport(repoRoot, repoName = path3.basename(repoRoot)) {
2830
2838
  const receiptCount = receipts.length + handoffs.length;
2831
2839
  const proofLedger = buildProofLedger(receipts, handoffs, proofLoop);
2832
2840
  const impact = impactReportFor(receipts);
2841
+ const valueSignals = receipts.reduce((summary, receipt) => {
2842
+ summary.reusablePrimitiveCount += receipt.valueSignals?.reusablePrimitiveCount ?? 0;
2843
+ summary.parserBackedEntityCount += receipt.valueSignals?.parserBackedEntityCount ?? 0;
2844
+ summary.appliedTeamGuidanceCount += receipt.valueSignals?.appliedTeamGuidanceCount ?? 0;
2845
+ summary.suggestedTestTargetCount += receipt.valueSignals?.suggestedTestCount ?? 0;
2846
+ return summary;
2847
+ }, {
2848
+ reusablePrimitiveCount: 0,
2849
+ parserBackedEntityCount: 0,
2850
+ appliedTeamGuidanceCount: 0,
2851
+ suggestedTestTargetCount: 0
2852
+ });
2833
2853
  const report = {
2834
2854
  repoName,
2835
2855
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2839,6 +2859,7 @@ function buildLocalValueReport(repoRoot, repoName = path3.basename(repoRoot)) {
2839
2859
  reviewCheckCount: receipts.filter((receipt) => receipt.kind === "review").length,
2840
2860
  handoffCount: handoffs.length,
2841
2861
  estimatedTokensSaved,
2862
+ ...valueSignals,
2842
2863
  averagePacketTokens,
2843
2864
  topSources,
2844
2865
  recentTasks: receipts.slice(0, 8).map((receipt) => ({
@@ -11948,14 +11969,14 @@ function contextForTask(scan, request) {
11948
11969
  const scoped = files.length > 0;
11949
11970
  const rankedRules = rankRules(scan.rules, task, files);
11950
11971
  const initialRules = rankedRules.slice(0, scoped ? 8 : 12);
11972
+ const initialSkills = selectSkills(scan.skills, task, files, scoped).map(compactSkillForContext);
11951
11973
  const mapCandidates = mapCandidatesForTask(scan, task, files);
11952
11974
  const rankedMap = rankMap(mapCandidates, task, files);
11953
- const initialGroundingRules = rulesStronglyRelevantForGrounding(initialRules, task);
11954
- const selectedMap = takeRuleGroundedMapNodes(mapCandidates, initialGroundingRules, rankedMap, scoped ? 14 : 18).map(compactMapNodeForContext);
11975
+ const selectedMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(initialRules, task), initialSkills, rankedMap, files, scoped ? 14 : 18).map(compactMapNodeForContext);
11955
11976
  const suggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, selectedMap, request.repoRoot);
11956
11977
  const initialSelection = {
11957
11978
  rules: initialRules,
11958
- skills: selectSkills(scan.skills, task, files, scoped).map(compactSkillForContext),
11979
+ skills: initialSkills,
11959
11980
  map: selectedMap,
11960
11981
  wikiPages: selectWikiPages(buildContextWiki(scan).pages, task, files, scoped, suggestedTests).map(compactWikiPageForContext),
11961
11982
  memories: rankMemories(scan.memories, task, files).slice(0, scoped ? 4 : 8).map(compactMemoryForContext),
@@ -11968,7 +11989,7 @@ function contextForTask(scan, request) {
11968
11989
  files
11969
11990
  });
11970
11991
  const { rules: selectedRules, skills: selectedSkills, map: budgetedMap, wikiPages: selectedWikiPages, memories: selectedMemories } = selection;
11971
- const finalMap = takeRuleGroundedMapNodes(mapCandidates, rulesStronglyRelevantForGrounding(selectedRules, task), budgetedMap, Math.max(budgetedMap.length, scoped ? 2 : 4)).map(compactMapNodeForContext);
11992
+ const finalMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(selectedRules, task), selectedSkills, budgetedMap, files, Math.max(budgetedMap.length, scoped ? 2 : 4)).map(compactMapNodeForContext);
11972
11993
  const finalSuggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, finalMap, request.repoRoot);
11973
11994
  const owners = selectOwners(scan.map, files);
11974
11995
  const sources = unique([
@@ -11978,6 +11999,7 @@ function contextForTask(scan, request) {
11978
11999
  ...selectedWikiPages.flatMap((page2) => page2.sourcePaths),
11979
12000
  ...selectedMemories.flatMap((memory) => memory.sourcePaths)
11980
12001
  ]);
12002
+ const teamGuidance = scan.teamGuidanceCatalog ? teamGuidanceDeliveryFor(scan, selectedRules, selectedSkills, selectedMemories) : void 0;
11981
12003
  const packet = {
11982
12004
  packetId: packetId(),
11983
12005
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11993,19 +12015,49 @@ function contextForTask(scan, request) {
11993
12015
  sources,
11994
12016
  tokenEstimate,
11995
12017
  omitted,
11996
- ...scan.teamGuidanceCatalog ? {
11997
- teamGuidance: teamGuidanceDeliveryFor(scan, selectedRules, selectedSkills, selectedMemories)
11998
- } : {}
12018
+ valueSignals: contextValueSignals(finalMap, finalSuggestedTests, sources.length, teamGuidance?.applied.length ?? 0),
12019
+ ...teamGuidance ? { teamGuidance } : {}
11999
12020
  };
12000
12021
  return enforceContextPacketBudget(packet, maxTokens, files);
12001
12022
  }
12023
+ function contextValueSignals(map, suggestedTests, relevantSourceCount, appliedTeamGuidanceCount) {
12024
+ const reusablePrimitiveCount = map.filter((node) => {
12025
+ if (!["component", "route", "source", "risk"].includes(node.type)) {
12026
+ return false;
12027
+ }
12028
+ const metadata = node.metadata ?? {};
12029
+ return [
12030
+ "exports",
12031
+ "astFunctions",
12032
+ "astClasses",
12033
+ "astComponents",
12034
+ "hooks",
12035
+ "services",
12036
+ "models",
12037
+ "commands",
12038
+ "domainEntities"
12039
+ ].some((key) => Array.isArray(metadata[key]) && metadata[key].length > 0);
12040
+ }).length;
12041
+ const parserBackedEntityCount = map.reduce((total, node) => {
12042
+ const value = node.metadata?.parserBackedEntityCount;
12043
+ return total + (typeof value === "number" && value > 0 ? value : 0);
12044
+ }, 0);
12045
+ return {
12046
+ relevantSourceCount,
12047
+ reusablePrimitiveCount,
12048
+ parserBackedEntityCount,
12049
+ appliedTeamGuidanceCount,
12050
+ suggestedTestCount: suggestedTests.length
12051
+ };
12052
+ }
12002
12053
  function codebaseMapForTask(scan, request, limit = 80) {
12003
12054
  const files = request.files ?? [];
12004
12055
  const hasSpecificScope = files.length > 0 || wordsFor3(request.task).length > 1;
12005
12056
  const mapCandidates = mapCandidatesForTask(scan, request.task, files);
12006
12057
  const rankedMap = rankMap(mapCandidates, request.task, files);
12007
- const groundedRules = rankRules(scan.rules, request.task, files).filter((rule) => ruleIsStronglyRelevantForGrounding(rule, request.task)).slice(0, 3);
12008
- const ruleGroundedMap = takeRuleGroundedMapNodes(mapCandidates, groundedRules, [], Math.min(8, limit));
12058
+ const selectedRules = rankRules(scan.rules, request.task, files).filter((rule) => ruleIsStronglyRelevantForGrounding(rule, request.task)).slice(0, files.length > 0 ? 8 : 12);
12059
+ const selectedSkills = selectSkills(scan.skills, request.task, files, files.length > 0);
12060
+ const ruleGroundedMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, selectedRules, selectedSkills, [], files, Math.min(8, limit));
12009
12061
  const selected = hasSpecificScope ? diversifyMap(uniqueMapNodes([...ruleGroundedMap, ...rankedMap]), limit) : broadCodebaseOverview(scan.map, limit);
12010
12062
  return withMapCoverageNotice(selected, scan, request, limit);
12011
12063
  }
@@ -12100,58 +12152,100 @@ function sourceFallbackMapNode(sourcePath) {
12100
12152
  }
12101
12153
  };
12102
12154
  }
12103
- function takeRuleGroundedMapNodes(scanMap, rules, rankedMap, limit) {
12104
- const groundedPaths = unique(rules.flatMap(explicitRuleGroundedPaths));
12105
- const groundedNodes = groundedPaths.flatMap((sourcePath) => {
12106
- const mappedNode = scanMap.find((node) => mapNodeSourcePath(node) === sourcePath);
12107
- return [
12108
- mappedNode ? markRuleGroundedMapNode(mappedNode) : ruleSourceFallbackMapNode(sourcePath)
12109
- ];
12155
+ function takeGuidanceGroundedMapNodes(scanMap, sources, rules, skills, rankedMap, files, limit) {
12156
+ const groundedPaths = guidanceReferencedSourcePaths(sources, rules, skills);
12157
+ const nodeBySourcePath = /* @__PURE__ */ new Map();
12158
+ for (const node of scanMap) {
12159
+ const sourcePath = mapNodeSourcePath(node);
12160
+ if (!nodeBySourcePath.has(sourcePath))
12161
+ nodeBySourcePath.set(sourcePath, node);
12162
+ }
12163
+ const explicitNodes = rankedMap.filter((node) => files.includes(mapNodeSourcePath(node)));
12164
+ const groundedNodes = groundedPaths.map((sourcePath) => {
12165
+ const mappedNode = nodeBySourcePath.get(sourcePath);
12166
+ return mappedNode ? markGuidanceGroundedMapNode(mappedNode) : guidanceSourceFallbackMapNode(sourcePath);
12110
12167
  });
12111
- const groundedLimit = Math.min(limit, 8, Math.max(3, Math.ceil(limit / 2)));
12112
12168
  return uniqueMapNodes([
12113
- ...groundedNodes.slice(0, groundedLimit),
12169
+ ...explicitNodes,
12170
+ ...groundedNodes,
12114
12171
  ...rankedMap
12115
12172
  ]).slice(0, limit);
12116
12173
  }
12117
- function explicitRuleGroundedPaths(rule) {
12118
- const codePaths = rule.sourcePaths.filter((sourcePath) => isCodeOrTestPath(sourcePath));
12119
- const normalizedBody = rule.body.toLowerCase().replaceAll("\\", "/");
12120
- const explicitPaths = codePaths.filter((sourcePath) => {
12121
- const normalizedPath = sourcePath.toLowerCase().replaceAll("\\", "/");
12122
- const basename = pathBasename(sourcePath);
12123
- const stem = basename.replace(/\.[^.]+$/, "");
12124
- return normalizedBody.includes(normalizedPath) || normalizedBody.includes(pathBasename(normalizedPath)) || /[A-Z_$]/.test(stem) && normalizedBody.includes(stem.toLowerCase());
12125
- });
12126
- return explicitPaths;
12174
+ function guidanceReferencedSourcePaths(sources, rules, skills) {
12175
+ const sourcePaths = new Map(sources.filter((source) => isCodeOrTestPath(source.path)).map((source) => [source.path.toLowerCase(), source.path]));
12176
+ const pathsByStem = /* @__PURE__ */ new Map();
12177
+ for (const sourcePath of sourcePaths.values()) {
12178
+ const rawStem = pathBasename(sourcePath).replace(/\.[^.]+$/, "");
12179
+ if (rawStem.length < 6 || !/[A-Z_$]/.test(rawStem))
12180
+ continue;
12181
+ const stem = rawStem.toLowerCase();
12182
+ pathsByStem.set(stem, [...pathsByStem.get(stem) ?? [], sourcePath]);
12183
+ }
12184
+ const guidanceText = [
12185
+ ...rules.map((rule) => `${rule.title}
12186
+ ${rule.body}`),
12187
+ ...skills.map((skill) => [
12188
+ skill.name,
12189
+ skill.description,
12190
+ skill.body,
12191
+ skill.triggers.join(" "),
12192
+ skill.teamShare?.rationale ?? "",
12193
+ skill.teamShare?.guardrails.join(" ") ?? ""
12194
+ ].join("\n"))
12195
+ ].join("\n");
12196
+ const selected = [];
12197
+ for (const rule of rules) {
12198
+ const body = rule.body.toLowerCase().replaceAll("\\", "/");
12199
+ for (const sourcePath of rule.sourcePaths) {
12200
+ if (!isCodeOrTestPath(sourcePath))
12201
+ continue;
12202
+ const normalizedPath = sourcePath.toLowerCase().replaceAll("\\", "/");
12203
+ const rawStem = pathBasename(sourcePath).replace(/\.[^.]+$/, "");
12204
+ if (body.includes(normalizedPath) || body.includes(pathBasename(normalizedPath)) || rawStem.length >= 6 && /[A-Z_$]/.test(rawStem) && body.includes(rawStem.toLowerCase())) {
12205
+ selected.push(sourcePath);
12206
+ }
12207
+ }
12208
+ }
12209
+ for (const match of guidanceText.matchAll(/[A-Za-z0-9_.@/-]+\.(?:[cm]?[tj]sx?|py|go|rb|sql|graphql|gql)\b/g)) {
12210
+ const referencedPath = match[0]?.replace(/^\.\//, "").toLowerCase();
12211
+ const canonicalPath = referencedPath ? sourcePaths.get(referencedPath) : void 0;
12212
+ if (canonicalPath)
12213
+ selected.push(canonicalPath);
12214
+ }
12215
+ for (const match of guidanceText.matchAll(/\b[A-Za-z_$][A-Za-z0-9_$-]{5,}\b/g)) {
12216
+ const matches = pathsByStem.get(match[0]?.toLowerCase() ?? "") ?? [];
12217
+ if (matches.length === 1)
12218
+ selected.push(matches[0]);
12219
+ }
12220
+ return unique(selected);
12127
12221
  }
12128
12222
  function pathBasename(sourcePath) {
12129
12223
  return sourcePath.split("/").at(-1) ?? sourcePath;
12130
12224
  }
12131
- function markRuleGroundedMapNode(node) {
12225
+ function markGuidanceGroundedMapNode(node) {
12132
12226
  return {
12133
12227
  ...node,
12134
12228
  metadata: {
12135
12229
  ...node.metadata ?? {},
12136
12230
  evidenceKinds: unique([
12137
12231
  ...metadataStringsForScore(node.metadata, "evidenceKinds"),
12138
- "rule_reference"
12232
+ "guidance_reference"
12139
12233
  ])
12140
12234
  }
12141
12235
  };
12142
12236
  }
12143
- function ruleSourceFallbackMapNode(sourcePath) {
12237
+ function guidanceSourceFallbackMapNode(sourcePath) {
12144
12238
  return {
12145
- id: idFor("map", `rule-source:${sourcePath}`),
12239
+ id: idFor("map", `guidance-source:${sourcePath}`),
12146
12240
  type: isLikelyTestPath(sourcePath) ? "test" : "source",
12147
12241
  name: titleFromPath(sourcePath),
12148
12242
  path: sourcePath,
12149
- summary: "Source file cited by a selected repo rule; included even when the broad map cap omits it.",
12150
- tags: unique(["source", ...fallbackTagsForPath(sourcePath)]),
12243
+ summary: "Source file cited by selected repo or team guidance; included even when the broad map cap omits it.",
12244
+ tags: unique(["source", "guidance", ...fallbackTagsForPath(sourcePath)]),
12151
12245
  metadata: {
12152
12246
  sourcePath,
12153
- evidenceKinds: ["rule_reference"],
12154
- entityConfidence: 72
12247
+ evidenceKinds: ["guidance_reference"],
12248
+ entityConfidence: 82
12155
12249
  }
12156
12250
  };
12157
12251
  }
@@ -12190,6 +12284,9 @@ function uniqueMapNodes(nodes) {
12190
12284
  function mapNodeSourcePath(node) {
12191
12285
  return typeof node.metadata?.sourcePath === "string" ? node.metadata.sourcePath : node.path;
12192
12286
  }
12287
+ function mapNodeHasGuidanceEvidence(node) {
12288
+ return metadataStringsForScore(node.metadata, "evidenceKinds").some((kind) => ["guidance_reference", "rule_reference"].includes(kind));
12289
+ }
12193
12290
  function isCodeOrTestPath(sourcePath) {
12194
12291
  return /\.(?:[cm]?[tj]sx?|py|go|rb|sql|graphql|gql)$/.test(sourcePath);
12195
12292
  }
@@ -12793,17 +12890,17 @@ function ruleIsStronglyRelevantForGrounding(rule, task) {
12793
12890
  function fitSelectionToBudget(initialSelection, options2) {
12794
12891
  const selection = cloneSelection(initialSelection);
12795
12892
  const omitted = [];
12796
- const protectedMapPaths = new Set(selection.map.filter((node) => mapNodeRelatesToExplicitFile(node, options2.files)).map(mapNodeSourcePath));
12893
+ const protectedMapPaths = new Set(selection.map.filter((node) => mapNodeRelatesToExplicitFile(node, options2.files) || mapNodeHasGuidanceEvidence(node)).map(mapNodeSourcePath));
12797
12894
  const minimums = {
12798
12895
  rules: options2.scoped ? Math.min(selection.rules.length, 3) : 5,
12799
- skills: 0,
12896
+ skills: selection.skills.some(isApprovedSharedSkill) ? 1 : 0,
12800
12897
  map: options2.scoped ? Math.min(selection.map.length, 8) : 10,
12801
12898
  wikiPages: options2.scoped ? Math.min(selection.wikiPages.length, 1) : 2,
12802
12899
  memories: 0
12803
12900
  };
12804
12901
  const hardMinimums = {
12805
12902
  rules: Math.min(selection.rules.length, options2.scoped ? 1 : 2),
12806
- skills: 0,
12903
+ skills: selection.skills.some(isApprovedSharedSkill) ? 1 : 0,
12807
12904
  map: Math.max(Math.min(selection.map.length, options2.scoped ? 2 : 4), protectedMapPaths.size),
12808
12905
  wikiPages: 0,
12809
12906
  memories: 0
@@ -12867,7 +12964,7 @@ function enforceContextPacketBudget(packet, maxTokens, explicitFiles) {
12867
12964
  let trimmed = packet.omitted.length > 0;
12868
12965
  if (trimmed)
12869
12966
  packet.omitted = [omittedReason];
12870
- const protectedMapPaths = new Set(packet.map.filter((node) => mapNodeRelatesToExplicitFile(node, explicitFiles)).map(mapNodeSourcePath));
12967
+ const protectedMapPaths = new Set(packet.map.filter((node) => mapNodeRelatesToExplicitFile(node, explicitFiles) || mapNodeHasGuidanceEvidence(node)).map(mapNodeSourcePath));
12871
12968
  let tokenEstimate = estimateContextPacketTokens(packet);
12872
12969
  const maxTrimSteps = removableContextItemCount(packet) + 1;
12873
12970
  let trimSteps = 0;
@@ -12993,20 +13090,37 @@ function compactProtectedMapMetadata(metadata) {
12993
13090
  "symbolKind",
12994
13091
  "symbolName",
12995
13092
  "role",
12996
- "entityConfidence"
13093
+ "entityConfidence",
13094
+ "parserBackedEntityCount"
12997
13095
  ]) {
12998
13096
  const value = metadata[key];
12999
13097
  if (value !== void 0)
13000
13098
  compact[key] = value;
13001
13099
  }
13002
13100
  for (const key of [
13101
+ "evidenceKinds",
13003
13102
  "exports",
13004
13103
  "imports",
13005
13104
  "importedBy",
13006
13105
  "relatedTests",
13007
13106
  "owners",
13008
13107
  "validationCommands",
13009
- "changeRisk"
13108
+ "changeRisk",
13109
+ "astClasses",
13110
+ "astCommands",
13111
+ "astComponents",
13112
+ "astFunctions",
13113
+ "astHooks",
13114
+ "astJobs",
13115
+ "astModels",
13116
+ "astServices",
13117
+ "commands",
13118
+ "domainEntities",
13119
+ "hooks",
13120
+ "jobs",
13121
+ "models",
13122
+ "procedures",
13123
+ "services"
13010
13124
  ]) {
13011
13125
  const value = metadata[key];
13012
13126
  if (Array.isArray(value))
@@ -13025,6 +13139,7 @@ function refreshContextPacketDerivedFields(packet) {
13025
13139
  suggestedTests: packet.suggestedTests
13026
13140
  });
13027
13141
  refreshTeamGuidanceDelivery(packet);
13142
+ packet.valueSignals = contextValueSignals(packet.map, packet.suggestedTests, packet.sources.length, packet.teamGuidance?.applied.length ?? 0);
13028
13143
  }
13029
13144
  function teamGuidanceDeliveryFor(scan, rules, skills, memories) {
13030
13145
  const catalog = scan.teamGuidanceCatalog;
@@ -13127,6 +13242,9 @@ function lastDroppableRuleIndex(rules) {
13127
13242
  }
13128
13243
  return -1;
13129
13244
  }
13245
+ function isApprovedSharedSkill(skill) {
13246
+ return skill.status === "approved";
13247
+ }
13130
13248
  function compactSkillForContext(skill) {
13131
13249
  return {
13132
13250
  ...skill,
@@ -13248,11 +13366,27 @@ var init_context = __esm({
13248
13366
  ]);
13249
13367
  contextMetadataKeys = /* @__PURE__ */ new Set([
13250
13368
  "accessControls",
13369
+ "agentTools",
13370
+ "astClasses",
13371
+ "astCommands",
13372
+ "astComponents",
13373
+ "astFunctions",
13374
+ "astHooks",
13375
+ "astJobs",
13376
+ "astModels",
13377
+ "astServices",
13251
13378
  "changeRisk",
13379
+ "commands",
13380
+ "domainEntities",
13252
13381
  "entityConfidence",
13253
13382
  "envVars",
13254
13383
  "evidenceKinds",
13255
13384
  "exports",
13385
+ "hooks",
13386
+ "jobs",
13387
+ "models",
13388
+ "parserBackedEntityCount",
13389
+ "procedures",
13256
13390
  "importedBy",
13257
13391
  "imports",
13258
13392
  "loopHints",
@@ -13260,6 +13394,7 @@ var init_context = __esm({
13260
13394
  "relatedTests",
13261
13395
  "role",
13262
13396
  "routes",
13397
+ "services",
13263
13398
  "sourcePath",
13264
13399
  "symbolKind",
13265
13400
  "symbolName",
@@ -26085,7 +26220,7 @@ var init_agentSetup = __esm({
26085
26220
  "ide",
26086
26221
  "generic"
26087
26222
  ];
26088
- briefPublishedPackageSpec = "runbrief@0.1.0";
26223
+ briefPublishedPackageSpec = "runbrief@0.1.1";
26089
26224
  TARGET_LABELS = {
26090
26225
  terminal: "Terminal agent",
26091
26226
  vscode: "VS Code",
@@ -27136,6 +27271,41 @@ var init_agentStart = __esm({
27136
27271
  });
27137
27272
 
27138
27273
  // ../core/dist/agentBeforeYouCode.js
27274
+ function summarizeBeforeYouCodeSharedContext(delivery) {
27275
+ if (!delivery) {
27276
+ return {
27277
+ status: "not_available",
27278
+ applied: [],
27279
+ availableCount: 0,
27280
+ omittedCount: 0,
27281
+ next: "Shared guidance is not available in this session; keep new learning personal until the workspace connection is ready."
27282
+ };
27283
+ }
27284
+ const next = delivery.status === "applied" ? "Use the applied shared guidance, then save a focused learning or reference when the pass proves a reusable pattern." : delivery.status === "available_not_applied" ? "Shared guidance exists but did not match this task; inspect the relevant team item before creating a new pattern." : "No approved shared guidance exists yet; keep this pass personal and publish only after the pattern is proven.";
27285
+ return {
27286
+ status: delivery.status,
27287
+ workspaceId: delivery.workspaceId,
27288
+ revision: delivery.revision,
27289
+ availableCount: delivery.availableCount,
27290
+ applied: delivery.applied.slice(0, 4),
27291
+ omittedCount: delivery.omittedCount,
27292
+ next
27293
+ };
27294
+ }
27295
+ function summarizeBeforeYouCodeReferences(beforeYouCode) {
27296
+ return {
27297
+ status: beforeYouCode.referenceFirst.status,
27298
+ availableCount: beforeYouCode.referenceFirst.availableCount,
27299
+ decision: beforeYouCode.referenceFirst.decision,
27300
+ references: beforeYouCode.referenceFirst.references.slice(0, 2).map((reference) => ({
27301
+ title: reference.title,
27302
+ kind: reference.kind,
27303
+ sourcePaths: reference.sourcePaths.slice(0, 4),
27304
+ useWhen: reference.useWhen.slice(0, 2),
27305
+ doNotCopy: reference.doNotCopy.slice(0, 2)
27306
+ }))
27307
+ };
27308
+ }
27139
27309
  function missionScopeFilesFromBeforeYouCode(beforeYouCode, requestedFiles = []) {
27140
27310
  const explicitFiles = unique(requestedFiles);
27141
27311
  if (explicitFiles.length > 0)
@@ -27170,6 +27340,7 @@ function buildBeforeYouCodeMap(scan, input) {
27170
27340
  files,
27171
27341
  maxTokens: 2500
27172
27342
  });
27343
+ const referenceFirst = referenceFirstFor(loadLocalReferenceImplementations(input.repoRoot), input.task);
27173
27344
  const taskAwareSources = scan.sources.map((source, index) => ({
27174
27345
  source,
27175
27346
  index,
@@ -27192,7 +27363,7 @@ function buildBeforeYouCodeMap(scan, input) {
27192
27363
  ...context.map.map((node) => ({
27193
27364
  ...node,
27194
27365
  evidenceKinds: Array.isArray(node.metadata?.evidenceKinds) ? node.metadata.evidenceKinds.filter((kind) => typeof kind === "string") : [],
27195
- priority: Array.isArray(node.metadata?.evidenceKinds) && node.metadata.evidenceKinds.includes("rule_reference") ? 3 : 0
27366
+ priority: Array.isArray(node.metadata?.evidenceKinds) && node.metadata.evidenceKinds.some((kind) => ["rule_reference", "guidance_reference"].includes(kind)) ? 3 : 0
27196
27367
  })),
27197
27368
  ...taskAwareSources.map((node) => ({ ...node, priority: 0 })),
27198
27369
  ...taskAwareMap.map((node) => ({ ...node, priority: 0 })),
@@ -27203,26 +27374,26 @@ function buildBeforeYouCodeMap(scan, input) {
27203
27374
  priority: 0
27204
27375
  }))
27205
27376
  ], input.task);
27206
- const mappedReuseCandidates = insights.agentPacket.reuseGuard.candidates.map((candidate, index) => ({
27377
+ const mappedReuseCandidateScores = insights.agentPacket.reuseGuard.candidates.map((candidate, index) => ({
27207
27378
  candidate,
27208
27379
  index,
27209
- intentScore: beforeYouCodePathIntentScore(candidate.path, input.task)
27210
- })).filter(({ intentScore }) => intentScore > -100).sort((a, b) => b.intentScore - a.intentScore || a.index - b.index).map(({ candidate }) => reuseCandidateFor(candidate));
27211
- const inspectReuseCandidates = inspectFirst.filter((item) => (beforeYouCodePathIntentScore(item.path, input.task) >= 40 || item.evidenceKinds.includes("rule_reference")) && !/\.(test|spec)\.[cm]?[tj]sx?$/i.test(item.path)).map((item) => ({
27380
+ intentScore: beforeYouCodeReuseScore(candidate, input.task)
27381
+ })).filter(({ candidate, intentScore }) => intentScore > 0 || intentScore >= 0 && candidate.evidenceKinds.some((kind) => ["ast", "parser"].includes(kind)) && candidate.symbols.length > 0).sort((a, b) => b.intentScore - a.intentScore || a.index - b.index).map(({ candidate }) => reuseCandidateFor(candidate));
27382
+ const inspectReuseCandidates = inspectFirst.filter((item) => (beforeYouCodePathIntentScore(item.path, input.task) >= 40 || item.evidenceKinds.some((kind) => ["rule_reference", "guidance_reference"].includes(kind))) && !/\.(test|spec)\.[cm]?[tj]sx?$/i.test(item.path)).map((item) => ({
27212
27383
  path: item.path,
27213
27384
  action: "Extend this existing implementation or rule it out.",
27214
27385
  why: item.why,
27215
27386
  evidenceKinds: item.evidenceKinds
27216
27387
  }));
27217
27388
  const reuseCandidates = uniqueReuseCandidates([
27218
- ...mappedReuseCandidates,
27389
+ ...mappedReuseCandidateScores,
27219
27390
  ...inspectReuseCandidates
27220
27391
  ]).sort((a, b) => {
27221
27392
  const inspectRank = (candidate) => {
27222
27393
  const index = inspectFirst.findIndex((item) => item.path === candidate.path);
27223
27394
  return index < 0 ? 0 : Math.max(0, 6 - index) * 60;
27224
27395
  };
27225
- return beforeYouCodePathIntentScore(b.path, input.task) + inspectRank(b) - (beforeYouCodePathIntentScore(a.path, input.task) + inspectRank(a));
27396
+ return beforeYouCodeReuseScore(b, input.task) + inspectRank(b) - (beforeYouCodeReuseScore(a, input.task) + inspectRank(a));
27226
27397
  }).slice(0, 5);
27227
27398
  const taskDerivedTestFiles = inspectFirstTestTargets(inspectFirst, prefersPrimaryWorkspaceTask(input.task));
27228
27399
  const testScopeFiles = files.length > 0 ? files : taskDerivedTestFiles.length > 0 ? taskDerivedTestFiles : target.changedFiles;
@@ -27245,7 +27416,7 @@ function buildBeforeYouCodeMap(scan, input) {
27245
27416
  const validationChecklist = validationChecklistFor({
27246
27417
  task: input.task,
27247
27418
  likelyTests,
27248
- reuseStatus: insights.agentPacket.reuseGuard.status
27419
+ reuseStatus: reuseStatusFor(reuseCandidates)
27249
27420
  });
27250
27421
  const productOperatingContext = buildProductOperatingContext(scan, {
27251
27422
  task: input.task,
@@ -27261,6 +27432,7 @@ function buildBeforeYouCodeMap(scan, input) {
27261
27432
  ...target.dirty && files.length === 0 ? ["Dirty worktree is broad; classify baseline before editing."] : []
27262
27433
  ]).slice(0, 5);
27263
27434
  const status = inspectFirst.length === 0 && reuseCandidates.length === 0 ? "needs_narrower_scope" : "ready";
27435
+ const reuseSummary = reuseCandidates.length > 0 ? `${reuseCandidates.length} focused reuse candidate${reuseCandidates.length === 1 ? "" : "s"} matched this task; extend or explicitly rule them out before creating new code.` : "No task-relevant reuse candidate is grounded yet; narrow the task or search once more before creating new code.";
27264
27436
  const headline = status === "ready" ? `${scan.repoName}: inspect ${inspectFirst.length} file${inspectFirst.length === 1 ? "" : "s"} and rule out ${reuseCandidates.length} reuse candidate${reuseCandidates.length === 1 ? "" : "s"} before coding.` : `${scan.repoName}: no focused code map yet; narrow by folder, feature, or proposed files before coding.`;
27265
27437
  const taskSize = taskSizeFor({
27266
27438
  task: input.task,
@@ -27276,6 +27448,8 @@ function buildBeforeYouCodeMap(scan, input) {
27276
27448
  reuseCandidates,
27277
27449
  likelyTests,
27278
27450
  productOperatingContext,
27451
+ ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
27452
+ referenceFirst,
27279
27453
  validationChecklist,
27280
27454
  stopSignals: stopSignals2
27281
27455
  });
@@ -27288,6 +27462,8 @@ function buildBeforeYouCodeMap(scan, input) {
27288
27462
  productOperatingContext,
27289
27463
  validationChecklist,
27290
27464
  stopSignals: stopSignals2,
27465
+ ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
27466
+ referenceFirst,
27291
27467
  maxLines: input.maxLines ?? 30
27292
27468
  });
27293
27469
  return {
@@ -27298,10 +27474,13 @@ function buildBeforeYouCodeMap(scan, input) {
27298
27474
  cockpit,
27299
27475
  inspectFirst,
27300
27476
  reuseFirst: {
27301
- status: insights.agentPacket.reuseGuard.status === "missing" && reuseCandidates.length > 0 ? "partial" : insights.agentPacket.reuseGuard.status,
27302
- summary: insights.agentPacket.reuseGuard.summary,
27303
- candidates: reuseCandidates
27477
+ status: reuseStatusFor(reuseCandidates),
27478
+ summary: reuseSummary,
27479
+ candidates: reuseCandidates,
27480
+ decision: reuseCandidates.length > 0 ? "For each candidate, choose reuse, adapt, or rule out and record the decision before creating a new primitive." : "No reuse candidate is grounded yet; run one focused search before creating a new primitive."
27304
27481
  },
27482
+ ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
27483
+ referenceFirst,
27305
27484
  likelyTests,
27306
27485
  exactTests,
27307
27486
  productOperatingContext,
@@ -27312,6 +27491,71 @@ function buildBeforeYouCodeMap(scan, input) {
27312
27491
  next: status === "ready" ? "Inspect these files and reuse candidates before editing; cite the reuse decision in the final handoff." : "Run brief.codebase_map or brief.context_for_task with a narrower task, folder, or file list before editing."
27313
27492
  };
27314
27493
  }
27494
+ function reuseStatusFor(candidates) {
27495
+ if (candidates.length === 0)
27496
+ return "missing";
27497
+ return candidates.length >= 3 || candidates.some((candidate) => candidate.evidenceKinds.includes("ast")) ? "strong" : "partial";
27498
+ }
27499
+ function beforeYouCodeReuseScore(candidate, task) {
27500
+ const candidateText2 = "symbols" in candidate ? [
27501
+ candidate.path,
27502
+ candidate.name,
27503
+ ...candidate.symbols,
27504
+ ...candidate.relatedEntities
27505
+ ].join(" ") : [candidate.path, candidate.action, candidate.why].join(" ");
27506
+ const taskTerms = new Set(reuseTerms(task));
27507
+ const candidateTerms = new Set(reuseTerms(candidateText2));
27508
+ const symbolMatches = [...taskTerms].filter((term) => candidateTerms.has(term)).length;
27509
+ return beforeYouCodePathIntentScore(candidate.path, task) + Math.min(48, symbolMatches * 12);
27510
+ }
27511
+ function reuseTerms(input) {
27512
+ return referenceQueryTerms(input.replace(/([a-z0-9])([A-Z])/g, "$1 $2"));
27513
+ }
27514
+ function referenceFirstFor(references, task) {
27515
+ const active = references.filter((reference) => reference.status !== "archived");
27516
+ const terms = referenceQueryTerms(task);
27517
+ const scored = active.map((reference, index) => ({
27518
+ reference,
27519
+ index,
27520
+ score: terms.reduce((score, term) => {
27521
+ const haystack = [
27522
+ reference.title,
27523
+ reference.summary,
27524
+ reference.kind,
27525
+ ...reference.tags,
27526
+ ...reference.appliesTo,
27527
+ ...reference.useWhen,
27528
+ ...reference.sourcePaths
27529
+ ].join(" ").toLowerCase();
27530
+ return score + (haystack.includes(term) ? 1 : 0);
27531
+ }, 0)
27532
+ })).sort((a, b) => b.score - a.score || b.reference.updatedAt.localeCompare(a.reference.updatedAt) || a.index - b.index);
27533
+ const matched = scored.filter((item) => item.score > 0).map((item) => item.reference);
27534
+ const selected = (matched.length > 0 ? matched : []).slice(0, 4);
27535
+ const status = selected.length > 0 ? "matched" : active.length > 0 ? "available_unmatched" : "none";
27536
+ return {
27537
+ status,
27538
+ availableCount: active.length,
27539
+ references: selected,
27540
+ decision: status === "matched" ? "Inspect the cited source paths and reuse or adapt the reference; honor its do-not-copy guardrails." : status === "available_unmatched" ? "Saved references exist, but none match this task; search the reference library before creating a new pattern." : "No saved reference matches this task; prove the new pattern before saving it for reuse."
27541
+ };
27542
+ }
27543
+ function referenceQueryTerms(input) {
27544
+ const ignored = /* @__PURE__ */ new Set([
27545
+ "after",
27546
+ "before",
27547
+ "create",
27548
+ "existing",
27549
+ "make",
27550
+ "new",
27551
+ "reuse",
27552
+ "task",
27553
+ "the",
27554
+ "this",
27555
+ "with"
27556
+ ]);
27557
+ return unique(input.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length >= 4 && !ignored.has(term)));
27558
+ }
27315
27559
  function testRecommendationMatchesWorkspace(recommendation, targetFiles) {
27316
27560
  const scopes = unique(targetFiles.map(workspaceScopeForFile).filter(Boolean));
27317
27561
  if (scopes.length === 0)
@@ -27344,7 +27588,13 @@ function cockpitFor(input) {
27344
27588
  ...input.inspectFirst[0] ? [`Inspect ${input.inspectFirst[0].path}.`] : ["Narrow the task until Brief can name an inspect-first file."],
27345
27589
  ...input.reuseCandidates[0] ? [`Reuse or rule out ${input.reuseCandidates[0].path}.`] : ["Search once more before creating a new primitive."]
27346
27590
  ]).slice(0, 4);
27347
- const productMemory = input.productOperatingContext.status === "not_applicable" ? [] : input.productOperatingContext.productMemoryCandidates.slice(0, 3);
27591
+ const sharedMemory = input.teamGuidance?.applied.slice(0, 2).map((item) => `Shared ${item.kind}: ${item.label}`) ?? [];
27592
+ const productMemory = unique([
27593
+ ...sharedMemory,
27594
+ ...input.referenceFirst.status === "matched" ? input.referenceFirst.references.slice(0, 1).map((reference) => `Saved reference: ${reference.title}`) : input.referenceFirst.status === "available_unmatched" ? ["Saved references exist, but none match this task yet."] : [],
27595
+ ...input.teamGuidance?.status === "available_not_applied" ? ["Shared guidance is available, but none matched this task yet."] : [],
27596
+ ...input.productOperatingContext.status === "not_applicable" ? [] : input.productOperatingContext.productMemoryCandidates
27597
+ ]).slice(0, 3);
27348
27598
  const reuseDecision = input.reuseCandidates.slice(0, 3).map((candidate) => `${candidate.path} - ${candidate.action}`);
27349
27599
  const validation = unique([
27350
27600
  ...input.likelyTests.slice(0, mode === "mission" ? 3 : 2),
@@ -27484,7 +27734,7 @@ function inspectFilesFor(nodes, contextMap, task) {
27484
27734
  })).filter(({ intentScore }) => intentScore > -100);
27485
27735
  const seen = /* @__PURE__ */ new Set();
27486
27736
  const candidates = [...fromNodes, ...fromContext];
27487
- const focusedCandidates = candidates.filter((item) => item.intentScore > 0);
27737
+ const focusedCandidates = candidates.filter((item) => item.intentScore > 0 || item.evidenceKinds.some((kind) => ["rule_reference", "guidance_reference"].includes(kind)));
27488
27738
  const ranked = (focusedCandidates.length >= 3 ? focusedCandidates : candidates).sort((a, b) => b.intentScore - a.intentScore || b.originPriority - a.originPriority || a.index - b.index).filter((item) => {
27489
27739
  const sourcePath = item.path.split("#", 1)[0] ?? item.path;
27490
27740
  if (seen.has(sourcePath))
@@ -27609,7 +27859,7 @@ function beforeYouCodePathIntentScore(path20, task) {
27609
27859
  const wantsBilling = /\b(bill(?:ing)?|checkout|paid|payment|price|pricing|stripe|subscription)\b/.test(lowerTask);
27610
27860
  const wantsWorkspaceBootstrap = /\b(onboarding|workspace creation|workspace bootstrap)\b/.test(lowerTask);
27611
27861
  const wantsConnector = /\b(agent action|connector|first useful|mcp|setup)\b/.test(lowerTask);
27612
- const wantsTeam = /\b(invite|member|team)\b/.test(lowerTask);
27862
+ const wantsTeam = /\b(invite|member|team (?:member|access|setup|management)|workspace team)\b/.test(lowerTask);
27613
27863
  const wantsAutoEngagement = /\b(auto[- ]?engage|automatically|by default|default engagement|reminder)\b/.test(lowerTask);
27614
27864
  const wantsRecommendations = /\b(context|recommendations?|reuse|tests?|test guidance|test recommendations?)\b/.test(lowerTask);
27615
27865
  if (wantsInterface && wantsLanding && /(landinginteractive|landing-page|src\/app\/page\.[cm]?[tj]sx?$)/.test(lowerPath)) {
@@ -27868,10 +28118,20 @@ function compactTextFor2(input) {
27868
28118
  `Target: ${input.target.repoName} | ${input.target.branch ?? "branch unknown"} | ${input.target.repoRoot}`,
27869
28119
  `Worktree: ${input.target.dirty ? "dirty" : "clean"}${input.target.changedFiles.length ? `; changed ${input.target.changedFiles.slice(0, 4).join(", ")}` : ""}${input.target.untrackedFiles.length ? `; untracked ${input.target.untrackedFiles.slice(0, 3).join(", ")}` : ""}`,
27870
28120
  ...input.target.requestedFiles.length ? [`Scope files: ${input.target.requestedFiles.slice(0, 5).join(", ")}`] : [],
28121
+ ...input.teamGuidance ? [
28122
+ `Shared guidance: ${input.teamGuidance.applied.length > 0 ? input.teamGuidance.applied.slice(0, 2).map((item) => item.label).join(", ") : input.teamGuidance.status === "available_not_applied" ? "available, but none matched this task" : "none approved yet"}`
28123
+ ] : [],
28124
+ ...input.referenceFirst.status === "matched" ? [
28125
+ `Saved reference: ${input.referenceFirst.references.slice(0, 2).map((reference) => reference.title).join(", ")}`
28126
+ ] : input.referenceFirst.status === "available_unmatched" ? [
28127
+ "Saved references exist, but none match this task; search before creating."
28128
+ ] : [],
27871
28129
  "Inspect first:",
27872
28130
  ...itemLines2(input.inspectFirst, (item) => `${item.path} - ${item.why}`, "No focused files yet; narrow the request.", 5),
27873
28131
  "Reuse before create:",
27874
28132
  ...itemLines2(input.reuseCandidates, (item) => `${item.path} - ${item.action}`, "No reuse candidates yet; run a narrower codebase_map query.", 4),
28133
+ "Reuse decision: reuse, adapt, or rule out each candidate before creating a new primitive.",
28134
+ `Reference decision: ${input.referenceFirst.decision}`,
27875
28135
  ...input.productOperatingContext.status === "not_applicable" ? [] : [
27876
28136
  "Product guardrails:",
27877
28137
  ...itemLines2(input.productOperatingContext.productRules, (item) => item.rule, "No product rules found; capture acceptance criteria before editing.", 3),
@@ -27899,6 +28159,7 @@ var init_agentBeforeYouCode = __esm({
27899
28159
  init_productOperatingContext();
27900
28160
  init_pathIntent();
27901
28161
  init_testRecommendations();
28162
+ init_localReferenceImplementations();
27902
28163
  init_utils();
27903
28164
  }
27904
28165
  });
@@ -28759,7 +29020,16 @@ function collectFindings(input) {
28759
29020
  findings.push(...bugBarFindings(input.diff, input.changedFiles));
28760
29021
  }
28761
29022
  if (input.policy.enabledGates.includes("tests")) {
28762
- findings.push(...missingTestFindings(input.changedFiles, input.context.suggestedTests, input.validationEvidence));
29023
+ const behaviorChangeInput = {
29024
+ changedFiles: input.changedFiles,
29025
+ diff: input.diff,
29026
+ suggestedTests: input.context.suggestedTests
29027
+ };
29028
+ if (input.validationEvidence) {
29029
+ behaviorChangeInput.validationEvidence = input.validationEvidence;
29030
+ }
29031
+ const behaviorFindings = behaviorChangeFindings(behaviorChangeInput);
29032
+ findings.push(...behaviorFindings.length > 0 ? [] : missingTestFindings(input.changedFiles, input.context.suggestedTests, input.validationEvidence), ...behaviorFindings);
28763
29033
  }
28764
29034
  if (input.policy.enabledGates.includes("runtime_evidence")) {
28765
29035
  const runtimeEvidenceInput = {
@@ -29767,6 +30037,50 @@ function missingTestFindings(changedFiles, suggestedTests, validationEvidence) {
29767
30037
  }
29768
30038
  ];
29769
30039
  }
30040
+ function behaviorChangeFindings(input) {
30041
+ const changedTestFiles = input.changedFiles.filter(isTestLikePath);
30042
+ if (changedTestFiles.length > 0)
30043
+ return [];
30044
+ const hits = addedLinesMatching(input.diff, /(?:\b(?:router\.(?:get|post|put|patch|delete|use)|app\.(?:get|post|put|patch|delete|use)|registerTool|publicProcedure|protectedProcedure|zaniaInternalProcedure|fetch|sendEmail|enqueue|publish|writeFile(?:Sync)?|requireAuth|authorize|permission|featureFlag|process\.env\.[A-Z0-9_]+)\b|\b(?:insert|update|delete|upsert)\s*\()/i).filter((hit) => !isTestLikePath(hit.file));
30045
+ if (hits.length === 0)
30046
+ return [];
30047
+ const proofTargets = input.suggestedTests.slice(0, 8);
30048
+ if (validationEvidenceCoversSuggestedTests(input.validationEvidence, proofTargets)) {
30049
+ return [];
30050
+ }
30051
+ const behaviorKinds = unique(hits.flatMap((hit) => behaviorKindForLine(hit.line))).slice(0, 5);
30052
+ const files = unique(hits.map((hit) => hit.file));
30053
+ return [
30054
+ {
30055
+ id: idFor("finding", `behavior-change:${locationsKey(hits)}:${behaviorKinds.join(",")}`),
30056
+ gateId: "tests",
30057
+ severity: "medium",
30058
+ title: `Behavior change: ${behaviorKinds.join(", ")} needs focused proof`,
30059
+ body: [
30060
+ `Brief found added ${behaviorKinds.join(", ")} signals in ${files.join(", ")}. This is more than a structural edit: it can change a user path, data write, access boundary, external side effect, or agent contract.`,
30061
+ proofTargets.length > 0 ? `Run the nearest mapped proof first: ${proofTargets.join(", ")}.` : "Add the nearest page, route, persistence, auth, or integration proof before handoff.",
30062
+ "A broad typecheck alone does not prove this behavior stayed correct."
30063
+ ].join(" "),
30064
+ files,
30065
+ ruleIds: [],
30066
+ suggestedTests: proofTargets
30067
+ }
30068
+ ];
30069
+ }
30070
+ function behaviorKindForLine(line) {
30071
+ const kinds = [];
30072
+ if (/\b(?:router\.|app\.|registerTool|publicProcedure|protectedProcedure|zaniaInternalProcedure)\b/i.test(line))
30073
+ kinds.push("route or agent contract");
30074
+ if (/\b(?:insert|update|delete|upsert)\s*\(/i.test(line))
30075
+ kinds.push("persistence");
30076
+ if (/\b(?:requireAuth|authorize|permission|process\.env\.)\b/i.test(line))
30077
+ kinds.push("access or configuration");
30078
+ if (/\b(?:fetch|sendEmail|enqueue|publish|writeFile)\b/i.test(line))
30079
+ kinds.push("external side effect");
30080
+ if (/\bfeatureFlag\b/i.test(line))
30081
+ kinds.push("feature flag");
30082
+ return kinds.length > 0 ? kinds : ["runtime behavior"];
30083
+ }
29770
30084
  function validationEvidenceCoversSuggestedTests(validationEvidence, suggestedTests) {
29771
30085
  const passedCommands = (validationEvidence ?? []).filter((item) => item.status === "passed").map((item) => item.command.toLowerCase());
29772
30086
  if (passedCommands.length === 0)
@@ -34644,7 +34958,7 @@ var init_installStatus = __esm({
34644
34958
  "../mcp/src/installStatus.ts"() {
34645
34959
  "use strict";
34646
34960
  init_serverInstructions();
34647
- briefConnectorVersion = "0.1.0";
34961
+ briefConnectorVersion = "0.1.1";
34648
34962
  }
34649
34963
  });
34650
34964
 
@@ -34675,6 +34989,11 @@ function buildCompactStartEnvelope(input) {
34675
34989
  path: item.path,
34676
34990
  action: compactLine(item.action, 160)
34677
34991
  })),
34992
+ reuseDecision: compactLine(beforeYouCode.reuseFirst.decision, 240),
34993
+ sharedContext: summarizeBeforeYouCodeSharedContext(
34994
+ beforeYouCode.teamGuidance
34995
+ ),
34996
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
34678
34997
  validation: beforeYouCode.likelyTests.slice(0, 3),
34679
34998
  result: input.result,
34680
34999
  stopSignals: beforeYouCode.stopSignals.slice(0, 2),
@@ -34700,6 +35019,9 @@ function formatCompactStartText(envelope) {
34700
35019
  ),
34701
35020
  "Reuse:",
34702
35021
  ...listLines(envelope.reuse.map((item) => `${item.path} - ${item.action}`)),
35022
+ `Reuse decision: ${envelope.reuseDecision}`,
35023
+ `Shared context: ${envelope.sharedContext.status === "applied" ? envelope.sharedContext.applied.map((item) => item.label).join(", ") : envelope.sharedContext.status}`,
35024
+ `Saved reference: ${envelope.referenceFirst.status === "matched" ? envelope.referenceFirst.references.map((reference) => reference.title).join(", ") : envelope.referenceFirst.status}`,
34703
35025
  "Validate:",
34704
35026
  ...listLines(envelope.validation),
34705
35027
  `Result: ${envelope.result.summary}`,
@@ -35142,7 +35464,7 @@ function renderWikiRefreshWorkflow(options2 = {}) {
35142
35464
  " with:",
35143
35465
  " node-version: 22",
35144
35466
  " - name: Refresh wiki",
35145
- ' run: npx -y runbrief@0.1.0 wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files',
35467
+ ' run: npx -y runbrief@0.1.1 wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files',
35146
35468
  " - name: Open refresh PR",
35147
35469
  " uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11",
35148
35470
  " with:",
@@ -35420,6 +35742,40 @@ var init_mapResponse = __esm({
35420
35742
  }
35421
35743
  });
35422
35744
 
35745
+ // ../mcp/src/scanFreshness.ts
35746
+ function scanFreshnessFor(repoRoot, scan) {
35747
+ const gitRoot = runGit(repoRoot, ["rev-parse", "--show-toplevel"]);
35748
+ const gitAvailable = Boolean(gitRoot);
35749
+ const branch = runGit(repoRoot, ["branch", "--show-current"]) || null;
35750
+ const commit = runGit(repoRoot, ["rev-parse", "HEAD"]) || null;
35751
+ const statusLines = (gitAvailable ? runGit(repoRoot, ["status", "--short"]) : "").split("\n").map((line) => line.trim()).filter(Boolean);
35752
+ return {
35753
+ status: !gitAvailable ? "git_unavailable" : scan.stats.mapPartial ? "partial_coverage" : statusLines.length > 0 ? "working_tree_changed" : "current_at_scan",
35754
+ git: {
35755
+ available: gitAvailable,
35756
+ detachedHead: gitAvailable && !branch
35757
+ },
35758
+ scannedAt: scan.scannedAt,
35759
+ branch,
35760
+ commit,
35761
+ changedPathCount: statusLines.length,
35762
+ changedPaths: statusLines.map((line) => line.replace(/^[ MADRCU?!]{1,3}\s+/, "")).slice(0, 24),
35763
+ mapCoverage: {
35764
+ nodes: scan.stats.mapNodeCount,
35765
+ candidates: scan.stats.mapTotalCandidateCount ?? scan.stats.mapNodeCount,
35766
+ omitted: scan.stats.mapOmittedCount ?? 0,
35767
+ partial: Boolean(scan.stats.mapPartial)
35768
+ },
35769
+ next: !gitAvailable ? "Git metadata is unavailable; verify the repo target before trusting map freshness." : statusLines.length > 0 ? "Rescan after the current edits settle; changed paths may invalidate reuse and wiki evidence." : scan.stats.mapPartial ? "Narrow the scan by area or raise maxFiles before treating the map as complete." : !branch ? "The map was generated from a detached commit and clean working tree; verify the intended branch before editing." : "The map was generated from the current commit and clean working tree."
35770
+ };
35771
+ }
35772
+ var init_scanFreshness = __esm({
35773
+ "../mcp/src/scanFreshness.ts"() {
35774
+ "use strict";
35775
+ init_dist2();
35776
+ }
35777
+ });
35778
+
35423
35779
  // ../mcp/src/workflowWiringResponse.ts
35424
35780
  function selectWorkflowWiringResponse(report, detail) {
35425
35781
  return detail === "expanded" ? report : summarizeAgentWorkflowWiring(report);
@@ -35712,6 +36068,7 @@ function summarizePacket(packet) {
35712
36068
  memories: packet.memories,
35713
36069
  sources: packet.sources,
35714
36070
  omitted: packet.omitted,
36071
+ ...packet.valueSignals ? { valueSignals: packet.valueSignals } : {},
35715
36072
  ...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {}
35716
36073
  };
35717
36074
  }
@@ -36147,8 +36504,13 @@ function summarizeBeforeYouCodeForStart(beforeYouCode) {
36147
36504
  reuseFirst: {
36148
36505
  status: beforeYouCode.reuseFirst.status,
36149
36506
  summary: beforeYouCode.reuseFirst.summary,
36507
+ decision: beforeYouCode.reuseFirst.decision,
36150
36508
  candidates: beforeYouCode.reuseFirst.candidates.slice(0, 2)
36151
36509
  },
36510
+ sharedContext: summarizeBeforeYouCodeSharedContext(
36511
+ beforeYouCode.teamGuidance
36512
+ ),
36513
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
36152
36514
  likelyTests: beforeYouCode.likelyTests.slice(0, 3),
36153
36515
  productOperatingContext: {
36154
36516
  status: beforeYouCode.productOperatingContext.status,
@@ -36307,10 +36669,11 @@ var init_server = __esm({
36307
36669
  init_wikiInit();
36308
36670
  init_workspace();
36309
36671
  init_handoffAutofill();
36672
+ init_scanFreshness();
36310
36673
  server = new McpServer(
36311
36674
  {
36312
36675
  name: "brief",
36313
- version: "0.1.0"
36676
+ version: "0.1.1"
36314
36677
  },
36315
36678
  {
36316
36679
  instructions: briefMcpInstructions
@@ -37115,6 +37478,7 @@ var init_server = __esm({
37115
37478
  },
37116
37479
  reuse: {
37117
37480
  status: beforeYouCode.reuseFirst.status,
37481
+ decision: beforeYouCode.reuseFirst.decision,
37118
37482
  candidates: beforeYouCode.reuseFirst.candidates.slice(0, 3).map(({ path: candidatePath, action, why }) => ({
37119
37483
  path: candidatePath,
37120
37484
  action,
@@ -37126,6 +37490,17 @@ var init_server = __esm({
37126
37490
  status: beforeYouCode.productOperatingContext.status,
37127
37491
  taskClasses: beforeYouCode.productOperatingContext.taskClasses
37128
37492
  },
37493
+ autoEngage: {
37494
+ status: "active",
37495
+ firstCall: "brief.next",
37496
+ beforeEditing: "brief.before_you_code or brief.start",
37497
+ beforeHandoff: "brief.review_diff",
37498
+ finalRecap: "one or two truthful lines; no receipt dump"
37499
+ },
37500
+ sharedContext: summarizeBeforeYouCodeSharedContext(
37501
+ beforeYouCode.teamGuidance
37502
+ ),
37503
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
37129
37504
  ...learnedWorkflow ? {
37130
37505
  learnedWorkflow: {
37131
37506
  name: learnedWorkflow.name,
@@ -37133,7 +37508,12 @@ var init_server = __esm({
37133
37508
  preservedGates: learnedWorkflow.preservedGates
37134
37509
  }
37135
37510
  } : {},
37136
- teamContext: { status: teamContext.status },
37511
+ teamContext: {
37512
+ status: teamContext.status,
37513
+ sharedContext: summarizeBeforeYouCodeSharedContext(
37514
+ beforeYouCode.teamGuidance
37515
+ )
37516
+ },
37137
37517
  detailHint: 'Call brief.next again with detail: "expanded" only for expanded proof and workflow diagnostics.',
37138
37518
  next: fullResponse.next
37139
37519
  });
@@ -37594,6 +37974,7 @@ var init_server = __esm({
37594
37974
  const cloudSync = await syncScanSummary(scan, repoRoot);
37595
37975
  return jsonText({
37596
37976
  ...summarizeScan(scan),
37977
+ scanFreshness: scanFreshnessFor(repoRoot, scan),
37597
37978
  rootResolution,
37598
37979
  repoTarget,
37599
37980
  cloudSync
@@ -39036,6 +39417,7 @@ init_startSummary();
39036
39417
  init_wikiInit();
39037
39418
  init_handoffAutofill();
39038
39419
  init_mapResponse();
39420
+ init_scanFreshness();
39039
39421
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
39040
39422
  import path19 from "node:path";
39041
39423
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -39239,6 +39621,7 @@ async function runCli(commandName, commandArgs) {
39239
39621
  repoTarget,
39240
39622
  scannedAt: scan.scannedAt,
39241
39623
  stats: scan.stats,
39624
+ scanFreshness: scanFreshnessFor(repoRoot, scan),
39242
39625
  cloudSync,
39243
39626
  webAppStatus: webAppStatusForScan(cloudSync),
39244
39627
  next: nextScanStep(cloudSync)
@@ -39325,6 +39708,7 @@ async function runCli(commandName, commandArgs) {
39325
39708
  },
39326
39709
  reuse: {
39327
39710
  status: beforeYouCode.reuseFirst.status,
39711
+ decision: beforeYouCode.reuseFirst.decision,
39328
39712
  candidates: beforeYouCode.reuseFirst.candidates.slice(0, 3).map(({ path: candidatePath, action, why }) => ({
39329
39713
  path: candidatePath,
39330
39714
  action,
@@ -39336,6 +39720,15 @@ async function runCli(commandName, commandArgs) {
39336
39720
  status: beforeYouCode.productOperatingContext.status,
39337
39721
  taskClasses: beforeYouCode.productOperatingContext.taskClasses
39338
39722
  },
39723
+ sharedContext: summarizeBeforeYouCodeSharedContext(
39724
+ beforeYouCode.teamGuidance
39725
+ ),
39726
+ autoEngage: {
39727
+ status: autoEngage.status,
39728
+ chatFeedback: autoEngage.chatFeedback.slice(0, 1),
39729
+ finalRecapPolicy: autoEngage.finalRecapPolicy
39730
+ },
39731
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
39339
39732
  ...learnedWorkflow ? {
39340
39733
  learnedWorkflow: {
39341
39734
  name: learnedWorkflow.name,
@@ -39343,7 +39736,12 @@ async function runCli(commandName, commandArgs) {
39343
39736
  preservedGates: learnedWorkflow.preservedGates
39344
39737
  }
39345
39738
  } : {},
39346
- teamContext: { status: teamContext.status },
39739
+ teamContext: {
39740
+ status: teamContext.status,
39741
+ sharedContext: summarizeBeforeYouCodeSharedContext(
39742
+ beforeYouCode.teamGuidance
39743
+ )
39744
+ },
39347
39745
  detailHint: "Run the same command with --full for expanded proof and workflow diagnostics.",
39348
39746
  next: fullResponse.next
39349
39747
  }
@@ -39803,6 +40201,10 @@ async function runCli(commandName, commandArgs) {
39803
40201
  repoTarget,
39804
40202
  cockpit: beforeYouCode.cockpit,
39805
40203
  beforeYouCode: summarizeBeforeYouCodeForCli(beforeYouCode),
40204
+ sharedContext: summarizeBeforeYouCodeSharedContext(
40205
+ beforeYouCode.teamGuidance
40206
+ ),
40207
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
39806
40208
  teamContext: summarizeTeamContext(teamContext),
39807
40209
  next: "Read cockpit.compactText first. For micro/small passes, stay lightweight unless scope grows; for medium/large passes, call brief.start or mission_plan before editing."
39808
40210
  });
@@ -40299,7 +40701,7 @@ async function runCli(commandName, commandArgs) {
40299
40701
  });
40300
40702
  return;
40301
40703
  }
40302
- if (isCommand(commandName, "review-diff", "review_diff")) {
40704
+ if (isCommand(commandName, "review-diff", "review", "review_diff")) {
40303
40705
  const baseRef = option2(commandArgs, "base");
40304
40706
  const task = option2(commandArgs, "task");
40305
40707
  const files = filesOption2(commandArgs);
@@ -40961,7 +41363,7 @@ function cliCommandHelp() {
40961
41363
  },
40962
41364
  {
40963
41365
  command: "review-diff",
40964
- aliases: ["review_diff"],
41366
+ aliases: ["review", "review_diff"],
40965
41367
  purpose: "Review the current diff for bugs, missing tests, reuse gaps, trust boundaries, design adherence, and proof.",
40966
41368
  repoBound: true,
40967
41369
  usage: 'brief review-diff --repo /path/to/repo --base main --task "Prepare PR" --json',
@@ -41686,6 +42088,7 @@ function printContextSummary(packet, localReceipt, teamContext) {
41686
42088
  `Sources: ${packet.sources.length}`,
41687
42089
  teamContextLine(teamContext),
41688
42090
  teamGuidanceLine(packet),
42091
+ valueSignalLine(packet),
41689
42092
  "",
41690
42093
  "Rules:",
41691
42094
  ...packet.rules.length > 0 ? packet.rules.slice(0, 8).map((rule) => `- [${rule.enforcement}] ${rule.title}`) : ["- none"],
@@ -41730,6 +42133,11 @@ function teamGuidanceLine(packet) {
41730
42133
  if (!delivery) return "Team guidance applied: not connected";
41731
42134
  return `Team guidance applied: ${delivery.applied.length}/${delivery.availableCount} approved items (${delivery.revision})`;
41732
42135
  }
42136
+ function valueSignalLine(packet) {
42137
+ const signals = packet.valueSignals;
42138
+ if (!signals) return "Value signals: not available";
42139
+ return `Value signals: ${signals.relevantSourceCount} relevant sources, ${signals.reusablePrimitiveCount} reusable primitives, ${signals.parserBackedEntityCount} parser-backed entities, ${signals.appliedTeamGuidanceCount} team guidance items, ${signals.suggestedTestCount} test targets`;
42140
+ }
41733
42141
  function writeHookFiles2(repoRoot, files) {
41734
42142
  const written = [];
41735
42143
  for (const file of files) {
@@ -41773,8 +42181,13 @@ function summarizeBeforeYouCodeForCli(beforeYouCode) {
41773
42181
  reuseFirst: {
41774
42182
  status: beforeYouCode.reuseFirst.status,
41775
42183
  summary: beforeYouCode.reuseFirst.summary,
42184
+ decision: beforeYouCode.reuseFirst.decision,
41776
42185
  candidates: beforeYouCode.reuseFirst.candidates.slice(0, 2)
41777
42186
  },
42187
+ sharedContext: summarizeBeforeYouCodeSharedContext(
42188
+ beforeYouCode.teamGuidance
42189
+ ),
42190
+ referenceFirst: summarizeBeforeYouCodeReferences(beforeYouCode),
41778
42191
  likelyTests: beforeYouCode.likelyTests.slice(0, 3),
41779
42192
  productOperatingContext: {
41780
42193
  status: beforeYouCode.productOperatingContext.status,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runbrief",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "Brief Connector: the local MCP companion for repo context, reuse guidance, review gates, and handoff proof.",
6
6
  "type": "module",