runbrief 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +25 -3
  2. package/dist/cli.js +1324 -625
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ var __esm = (fn, res) => function __init() {
6
6
 
7
7
  // ../contracts/dist/index.js
8
8
  import { z } from "zod";
9
- var privacyModeSchema, enforcementSchema, skillStatusSchema, learningStatusSchema, findingSeveritySchema, prReadinessVerdictSchema, preCodingContextProofSchema, prReadinessCheckStatusSchema, prReadinessGateIdSchema, prReadinessGateStatusSchema, prReadinessStatusSchema, prReadinessDeliveryModeSchema, githubCheckConclusionSchema, agentSurfaceModeSchema, agentSurfaceCategorySchema, agentSurfaceCredentialStorageSchema, agentSurfaceRepoResolutionSchema, agentSurfaceTrustBoundarySchema, briefProofStateSchema, briefToolNameSchema, agentSurfaceContractSchema, briefRunPhaseSchema, briefRunContractSchema, briefProofContractSchema, repoRefSchema, scanStatsSchema, sourceDocumentSchema, ruleSchema, betaReadinessSchema, skillSchema, mapNodeSchema, reviewMemorySchema, wikiEvidenceItemSchema, wikiPageTypeSchema, wikiPageEvidenceMixSchema, wikiPageEntitySchema, wikiPageFreshnessSchema, wikiPageSourceProofSchema, wikiPageAgentBriefSchema, wikiPageQualitySchema, wikiPageSchema, prReadinessFindingSchema, prReadinessPullRequestSchema, prReadinessContextProofInputSchema, prReadinessCheckSchema, prReadinessGateResultSchema, prReadinessContractSchema, prReadinessReportRequestSchema, severityCountsSchema, prReadinessAnnotationSchema, prReadinessGithubCheckSchema, prReadinessReportSchema, prReadinessReportResponseSchema, repoScanSummarySchema, syncScanRequestSchema, teamGuidanceItemSchema, teamGuidanceDeliverySchema, contextPacketSchema, contextReceiptRequestSchema, missionRecipeIdSchema, missionWorkItemSchema, missionRuntimeEvidenceSchema, runtimeEvidencePullInstructionSchema, runtimeEvidencePacketSchema, missionHandoffValidationSchema, missionHandoffReviewReadinessSchema, missionHandoffReviewSchema, missionHandoffProofStatusSchema, missionHandoffValidationSummarySchema, missionHandoffArtifactEvidenceSchema, missionHandoffArtifactProofSchema, missionHandoffChangedFileAttributionSchema, missionHandoffProofBundleSchema, missionHandoffRequestSchema, evidenceRedactionFindingSchema, evidenceRedactionReportSchema, learningCreateRequestSchema, teamContextRequestSchema, teamContextResponseSchema, apiAcceptedResponseSchema;
9
+ var privacyModeSchema, loopRunStatusSchema, loopRunProofSchema, loopRunSummarySchema, createLoopRunRequestSchema, claimLoopRunRequestSchema, updateLoopRunRequestSchema, enforcementSchema, skillStatusSchema, learningStatusSchema, findingSeveritySchema, prReadinessVerdictSchema, preCodingContextProofSchema, prReadinessCheckStatusSchema, prReadinessGateIdSchema, prReadinessGateStatusSchema, prReadinessStatusSchema, prReadinessDeliveryModeSchema, githubCheckConclusionSchema, agentSurfaceModeSchema, agentSurfaceCategorySchema, agentSurfaceCredentialStorageSchema, agentSurfaceRepoResolutionSchema, agentSurfaceTrustBoundarySchema, briefProofStateSchema, briefToolNameSchema, agentSurfaceContractSchema, briefRunPhaseSchema, briefRunContractSchema, briefProofContractSchema, repoRefSchema, scanStatsSchema, sourceDocumentSchema, ruleSchema, betaReadinessSchema, skillSchema, mapNodeSchema, reviewMemorySchema, wikiEvidenceItemSchema, wikiPageTypeSchema, wikiPageEvidenceMixSchema, wikiPageEntitySchema, wikiPageFreshnessSchema, wikiPageSourceProofSchema, wikiPageAgentBriefSchema, wikiPageQualitySchema, wikiPageSchema, prReadinessFindingSchema, prReadinessPullRequestSchema, prReadinessContextProofInputSchema, prReadinessCheckSchema, prReadinessGateResultSchema, prReadinessContractSchema, prReadinessReportRequestSchema, severityCountsSchema, prReadinessAnnotationSchema, prReadinessGithubCheckSchema, prReadinessReportSchema, prReadinessReportResponseSchema, repoScanSummarySchema, syncScanRequestSchema, teamGuidanceItemSchema, teamGuidanceDeliverySchema, dependencyPackageSignalSchema, dependencyRemediationSchema, contextPacketSchema, contextReceiptRequestSchema, missionRecipeIdSchema, missionWorkItemSchema, missionRuntimeEvidenceSchema, runtimeEvidencePullInstructionSchema, runtimeEvidencePacketSchema, missionHandoffValidationSchema, missionHandoffReviewReadinessSchema, missionHandoffReviewSchema, missionHandoffProofStatusSchema, missionHandoffValidationSummarySchema, missionHandoffArtifactEvidenceSchema, missionHandoffArtifactProofSchema, missionHandoffChangedFileAttributionSchema, missionHandoffProofBundleSchema, missionHandoffRequestSchema, evidenceRedactionFindingSchema, evidenceRedactionReportSchema, learningCreateRequestSchema, teamContextRequestSchema, teamContextResponseSchema, apiAcceptedResponseSchema;
10
10
  var init_dist = __esm({
11
11
  "../contracts/dist/index.js"() {
12
12
  "use strict";
@@ -15,6 +15,56 @@ var init_dist = __esm({
15
15
  "cloud-sync-derived",
16
16
  "team-cloud"
17
17
  ]);
18
+ loopRunStatusSchema = z.enum([
19
+ "queued",
20
+ "running",
21
+ "succeeded",
22
+ "failed",
23
+ "cancelled"
24
+ ]);
25
+ loopRunProofSchema = z.record(z.string(), z.unknown());
26
+ loopRunSummarySchema = z.object({
27
+ id: z.string().uuid(),
28
+ workspaceId: z.string().uuid(),
29
+ loopId: z.string().uuid(),
30
+ loopName: z.string().min(1),
31
+ loopBody: z.string().min(1),
32
+ guardrails: z.array(z.string().min(1)).max(30),
33
+ loopVersion: z.number().int().positive(),
34
+ task: z.string().min(1).max(12e3),
35
+ repoFullName: z.string().min(1).max(240).optional(),
36
+ autonomy: z.enum(["read_only", "repo_edit", "validate", "handoff"]),
37
+ status: loopRunStatusSchema,
38
+ attemptCount: z.number().int().nonnegative(),
39
+ runnerId: z.string().min(1).max(160).nullable(),
40
+ leaseExpiresAt: z.string().datetime().nullable(),
41
+ lastEvent: z.string().max(2e3).nullable(),
42
+ resultSummary: z.string().max(12e3).nullable(),
43
+ proof: loopRunProofSchema,
44
+ errorMessage: z.string().max(4e3).nullable(),
45
+ createdAt: z.string().datetime(),
46
+ startedAt: z.string().datetime().nullable(),
47
+ completedAt: z.string().datetime().nullable()
48
+ });
49
+ createLoopRunRequestSchema = z.object({
50
+ loopId: z.string().uuid(),
51
+ task: z.string().trim().min(1).max(12e3),
52
+ repoFullName: z.string().trim().min(1).max(240).optional(),
53
+ idempotencyKey: z.string().trim().min(16).max(160)
54
+ });
55
+ claimLoopRunRequestSchema = z.object({
56
+ runnerId: z.string().trim().min(1).max(160),
57
+ leaseSeconds: z.number().int().min(30).max(900).default(180)
58
+ });
59
+ updateLoopRunRequestSchema = z.object({
60
+ runnerId: z.string().trim().min(1).max(160),
61
+ status: loopRunStatusSchema,
62
+ lastEvent: z.string().trim().max(2e3).optional(),
63
+ resultSummary: z.string().trim().max(12e3).optional(),
64
+ proof: loopRunProofSchema.optional(),
65
+ errorMessage: z.string().trim().max(4e3).optional(),
66
+ leaseSeconds: z.number().int().min(30).max(900).optional()
67
+ });
18
68
  enforcementSchema = z.enum([
19
69
  "advisory",
20
70
  "warn",
@@ -621,6 +671,46 @@ var init_dist = __esm({
621
671
  applied: z.array(teamGuidanceItemSchema).max(100),
622
672
  omittedCount: z.number().int().nonnegative()
623
673
  });
674
+ dependencyPackageSignalSchema = z.object({
675
+ name: z.string().min(1).max(160),
676
+ changeKind: z.enum([
677
+ "patch_or_minor",
678
+ "major",
679
+ "added_or_overridden",
680
+ "unknown"
681
+ ]),
682
+ manifestPaths: z.array(z.string().min(1).max(500)).max(20),
683
+ lockfilePaths: z.array(z.string().min(1).max(500)).max(20),
684
+ evidence: z.array(z.string().min(1).max(120)).max(10)
685
+ });
686
+ dependencyRemediationSchema = z.object({
687
+ id: z.string().min(1).max(120),
688
+ status: z.enum(["ready", "needs_evidence"]),
689
+ packageManager: z.enum([
690
+ "pnpm",
691
+ "npm",
692
+ "yarn",
693
+ "bun",
694
+ "pip",
695
+ "bundler",
696
+ "go",
697
+ "cargo",
698
+ "unknown"
699
+ ]),
700
+ manifestFiles: z.array(z.string().min(1).max(500)).max(30),
701
+ lockfiles: z.array(z.string().min(1).max(500)).max(20),
702
+ workspaceFiles: z.array(z.string().min(1).max(500)).max(20),
703
+ changedDependencyFiles: z.array(z.string().min(1).max(500)).max(30),
704
+ directDependencyFiles: z.array(z.string().min(1).max(500)).max(30),
705
+ packageSignals: z.array(dependencyPackageSignalSchema).max(20),
706
+ graphCommands: z.array(z.string().min(1).max(500)).max(20),
707
+ remediationCommands: z.array(z.string().min(1).max(500)).max(20),
708
+ validationCommands: z.array(z.string().min(1).max(500)).max(20),
709
+ residualRiskChecks: z.array(z.string().min(1).max(800)).max(20),
710
+ handoffRequirements: z.array(z.string().min(1).max(800)).max(20),
711
+ summary: z.string().max(1200),
712
+ confidence: z.number().min(0).max(100)
713
+ });
624
714
  contextPacketSchema = z.object({
625
715
  packetId: z.string().min(1),
626
716
  createdAt: z.string().datetime(),
@@ -643,7 +733,8 @@ var init_dist = __esm({
643
733
  appliedTeamGuidanceCount: z.number().int().nonnegative(),
644
734
  suggestedTestCount: z.number().int().nonnegative()
645
735
  }).optional(),
646
- teamGuidance: teamGuidanceDeliverySchema.optional()
736
+ teamGuidance: teamGuidanceDeliverySchema.optional(),
737
+ dependencyRemediation: dependencyRemediationSchema.optional()
647
738
  });
648
739
  contextReceiptRequestSchema = z.object({
649
740
  workspaceId: z.string().uuid().optional(),
@@ -5296,22 +5387,22 @@ function likelyRuleLine(line) {
5296
5387
  return hasStrongRule || hasOperationalRule;
5297
5388
  }
5298
5389
  function scopeFromPath(sourcePath, kind) {
5299
- const path20 = sourcePath.replaceAll("\\", "/");
5300
- const directoryScope = scopedDirectory(path20);
5301
- if (directoryScope && (path20.endsWith("/AGENTS.md") || path20.endsWith("/CLAUDE.md")))
5390
+ const path21 = sourcePath.replaceAll("\\", "/");
5391
+ const directoryScope = scopedDirectory(path21);
5392
+ if (directoryScope && (path21.endsWith("/AGENTS.md") || path21.endsWith("/CLAUDE.md")))
5302
5393
  return [directoryScope];
5303
- if (path20.includes("client/") || kind === "frontend")
5394
+ if (path21.includes("client/") || kind === "frontend")
5304
5395
  return ["client/**", "packages/ui/**"];
5305
- if (path20.includes("server/") || kind === "persistence")
5396
+ if (path21.includes("server/") || kind === "persistence")
5306
5397
  return ["server/**"];
5307
- if (path20.includes(".github/") || kind === "workflow")
5398
+ if (path21.includes(".github/") || kind === "workflow")
5308
5399
  return ["**/*"];
5309
- if (path20.includes("test") || kind === "testing")
5400
+ if (path21.includes("test") || kind === "testing")
5310
5401
  return ["**/*.test.*", "**/*.spec.*", "tests/**"];
5311
5402
  return ["**/*"];
5312
5403
  }
5313
- function scopedDirectory(path20) {
5314
- const parts = path20.split("/");
5404
+ function scopedDirectory(path21) {
5405
+ const parts = path21.split("/");
5315
5406
  if (parts.length <= 1)
5316
5407
  return null;
5317
5408
  const directory = parts.slice(0, -1).join("/");
@@ -8605,7 +8696,7 @@ function agentPacketPrompt(input) {
8605
8696
  ].filter(Boolean).join(" ");
8606
8697
  }
8607
8698
  function inspectReasonForNode(node) {
8608
- const path20 = pathKey(node);
8699
+ const path21 = pathKey(node);
8609
8700
  const systems = metadataStrings2(node, "systems");
8610
8701
  const tables = metadataStrings2(node, "tables");
8611
8702
  const access = metadataStrings2(node, "accessControls");
@@ -8625,7 +8716,7 @@ function inspectReasonForNode(node) {
8625
8716
  return "Entrypoint that frames runtime flow.";
8626
8717
  if (node.type === "component")
8627
8718
  return "UI component surface in mapped scope.";
8628
- return `Mapped ${node.type} node for ${path20}.`;
8719
+ return `Mapped ${node.type} node for ${path21}.`;
8629
8720
  }
8630
8721
  function strongestEntityEvidenceKind(nodes, kind, name) {
8631
8722
  const matchingNodes = nodes.filter((node) => metadataStrings2(node, metadataKeyForEntity(kind)).includes(name));
@@ -8698,17 +8789,17 @@ function topFacet(nodes, key, limit) {
8698
8789
  function topAreaFacet(nodes, limit) {
8699
8790
  const areas = /* @__PURE__ */ new Map();
8700
8791
  for (const node of nodes) {
8701
- const path20 = pathKey(node);
8702
- const prefix = areaPrefix(path20);
8792
+ const path21 = pathKey(node);
8793
+ const prefix = areaPrefix(path21);
8703
8794
  const entry = areas.get(prefix) ?? {
8704
8795
  paths: /* @__PURE__ */ new Set(),
8705
8796
  codeSurfaceCount: 0,
8706
8797
  systems: /* @__PURE__ */ new Set()
8707
8798
  };
8708
- if (!entry.paths.has(path20) && CODE_SURFACE_TYPES.has(node.type)) {
8799
+ if (!entry.paths.has(path21) && CODE_SURFACE_TYPES.has(node.type)) {
8709
8800
  entry.codeSurfaceCount += 1;
8710
8801
  }
8711
- entry.paths.add(path20);
8802
+ entry.paths.add(path21);
8712
8803
  for (const system of metadataStrings2(node, "systems")) {
8713
8804
  entry.systems.add(system);
8714
8805
  }
@@ -10564,7 +10655,7 @@ function wikiBuildPipeline(scan, pages, operationalAtlas, loopPlaybooks, refresh
10564
10655
  },
10565
10656
  {
10566
10657
  id: "publish",
10567
- title: "Publish for loops",
10658
+ title: "Share with loops",
10568
10659
  status: "ready",
10569
10660
  summary: "Mission planning and context packets can cite wiki pages as source-grounded operating context.",
10570
10661
  evidence: ["brief.wiki_outline", "brief.context_for_task"]
@@ -10992,7 +11083,7 @@ function loopPlaybookSpec(loopId) {
10992
11083
  if (loopId === "session_library") {
10993
11084
  return {
10994
11085
  title: "Session library loop",
10995
- summary: "Promote proven agent runs into reusable team skills, loops, styles, and run cards without publishing raw transcripts by default.",
11086
+ summary: "Promote proven agent runs into reusable team skills, loops, styles, and run cards without sharing raw transcripts by default.",
10996
11087
  tools: [
10997
11088
  "brief.agent_session_insights",
10998
11089
  "brief.value_report",
@@ -11013,7 +11104,7 @@ function loopPlaybookSpec(loopId) {
11013
11104
  ],
11014
11105
  privacyBoundary: [
11015
11106
  ...common.privacyBoundary,
11016
- "Publish concise run cards and styles, not raw private sessions, unless explicitly approved."
11107
+ "Share concise run cards and styles, not raw private sessions, unless explicitly approved."
11017
11108
  ],
11018
11109
  systemHints: ["github", "ci"],
11019
11110
  queryHint: "agent session receipts run cards skills loops styles",
@@ -11204,7 +11295,7 @@ function wikiUsageReceipts(pages, options2) {
11204
11295
  loopId: "session_library",
11205
11296
  tool: "brief.mission_plan",
11206
11297
  pageIds: pageIds("Session Library", "Review Memory", "Team Standards", "Maintainers"),
11207
- reason: "Session library loops need approved examples, privacy decisions, maintainer context, and reusable team standards before publishing run cards or skills."
11298
+ reason: "Session library loops need approved examples, privacy decisions, maintainer context, and reusable team standards before sharing run cards or skills."
11208
11299
  },
11209
11300
  {
11210
11301
  id: `wiki_use_${contentHash("docs:overview:structure")}`,
@@ -11387,9 +11478,9 @@ var init_contextWiki = __esm({
11387
11478
  });
11388
11479
 
11389
11480
  // ../core/dist/pathIntent.js
11390
- function namedCodePathIntentScore(path20, task) {
11481
+ function namedCodePathIntentScore(path21, task) {
11391
11482
  const normalizedTask = task.toLowerCase().replace(/[_-]+/g, " ");
11392
- const normalizedPath = (path20.split("#", 1)[0] ?? path20).toLowerCase();
11483
+ const normalizedPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
11393
11484
  let score = 0;
11394
11485
  const namedTargets = [
11395
11486
  [/\breview diff\b/, /diffreview|diff[-_]review/],
@@ -11535,9 +11626,476 @@ var init_pathIntent = __esm({
11535
11626
  }
11536
11627
  });
11537
11628
 
11629
+ // ../core/dist/dependencyRemediation.js
11630
+ import path6 from "node:path";
11631
+ function buildDependencyRemediationPlan(repoRoot, request) {
11632
+ const files = walkFiles(repoRoot, request.maxFiles ?? 6e3);
11633
+ const manifests = files.filter(isDependencyManifest);
11634
+ const lockfiles = files.filter(isDependencyLockfile);
11635
+ const workspaceFiles = files.filter(isDependencyWorkspaceFile);
11636
+ const changedDependencyFiles = unique((request.changedFiles ?? []).filter(isDependencyFile));
11637
+ const packageManager = inferPackageManager(repoRoot, manifests, lockfiles);
11638
+ const packageSignalInput = {
11639
+ task: request.task,
11640
+ manifests,
11641
+ changedDependencyFiles
11642
+ };
11643
+ if (request.diff)
11644
+ packageSignalInput.diff = request.diff;
11645
+ const packageSignals = packageSignalsFor(repoRoot, packageSignalInput);
11646
+ const directDependencyFiles = unique(packageSignals.flatMap((signal2) => signal2.manifestPaths));
11647
+ const packageNames = packageSignals.map((signal2) => signal2.name);
11648
+ const validationCommands2 = validationCommandsFor2(repoRoot, packageManager);
11649
+ const graphCommands = dependencyGraphCommandsFor(packageManager, packageNames);
11650
+ const remediationCommands = remediationCommandsFor(packageManager, packageNames);
11651
+ const residualRiskChecks = residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles);
11652
+ const confidence = confidenceFor({
11653
+ changedDependencyFiles,
11654
+ packageSignals,
11655
+ lockfiles,
11656
+ validationCommands: validationCommands2
11657
+ });
11658
+ return {
11659
+ id: `dep_${contentHash([
11660
+ request.task,
11661
+ packageManager,
11662
+ changedDependencyFiles.join(","),
11663
+ packageNames.join(",")
11664
+ ].join(":"))}`,
11665
+ status: changedDependencyFiles.length > 0 || packageSignals.length > 0 ? "ready" : "needs_evidence",
11666
+ packageManager,
11667
+ manifestFiles: manifests.slice(0, 20),
11668
+ lockfiles,
11669
+ workspaceFiles,
11670
+ changedDependencyFiles,
11671
+ directDependencyFiles,
11672
+ packageSignals,
11673
+ graphCommands,
11674
+ remediationCommands,
11675
+ validationCommands: validationCommands2,
11676
+ residualRiskChecks,
11677
+ handoffRequirements: [
11678
+ "List advisory ids or scanner finding ids that were fixed.",
11679
+ "Name packages still present in the lockfile with vulnerable versions, or state that the scan/audit is clean.",
11680
+ "Explain any major-version upgrades, overrides, peer dependency changes, skipped advisories, or remaining advisory ids.",
11681
+ "Attach validation commands and CI follow-up results before marking the security mission done."
11682
+ ],
11683
+ summary: summaryFor3({
11684
+ packageManager,
11685
+ manifests,
11686
+ lockfiles,
11687
+ changedDependencyFiles,
11688
+ packageSignals,
11689
+ validationCommands: validationCommands2
11690
+ }),
11691
+ confidence
11692
+ };
11693
+ }
11694
+ function isDependencyRemediationTask(task, files = []) {
11695
+ const normalized = task.toLowerCase();
11696
+ const hasSecuritySignal = /\b(dependabot|vulnerab\w*|cve-|ghsa-|security advisory|security finding|audit finding|osv|sca|dependency-review)\b/.test(normalized);
11697
+ const hasDependencyChangeSignal = /\b(dependency|dependencies|lockfile|package upgrade|package update|package bump|transitive|override|resolution)\b/.test(normalized);
11698
+ return hasSecuritySignal || hasDependencyChangeSignal && files.some(isDependencyFile);
11699
+ }
11700
+ function isDependencyFile(filePath) {
11701
+ const normalized = filePath.replaceAll("\\", "/");
11702
+ return dependencyFilePatterns.some((pattern) => normalized === pattern || normalized.endsWith(`/${pattern}`) || pattern === "package.json" && normalized.endsWith("/package.json"));
11703
+ }
11704
+ function isDependencyLockfile(filePath) {
11705
+ const normalized = filePath.replaceAll("\\", "/");
11706
+ return normalized === "pnpm-lock.yaml" || normalized.endsWith("/pnpm-lock.yaml") || normalized === "package-lock.json" || normalized.endsWith("/package-lock.json") || normalized === "yarn.lock" || normalized.endsWith("/yarn.lock") || normalized === "bun.lock" || normalized.endsWith("/bun.lock") || normalized === "npm-shrinkwrap.json" || normalized.endsWith("/npm-shrinkwrap.json") || normalized === "poetry.lock" || normalized.endsWith("/poetry.lock") || normalized === "Pipfile.lock" || normalized.endsWith("/Pipfile.lock") || normalized === "Gemfile.lock" || normalized.endsWith("/Gemfile.lock") || normalized === "go.sum" || normalized.endsWith("/go.sum") || normalized === "Cargo.lock" || normalized.endsWith("/Cargo.lock");
11707
+ }
11708
+ function isDependencyManifest(filePath) {
11709
+ const normalized = filePath.replaceAll("\\", "/");
11710
+ return normalized === "package.json" || normalized.endsWith("/package.json") || normalized === "requirements.txt" || normalized.endsWith("/requirements.txt") || normalized === "pyproject.toml" || normalized.endsWith("/pyproject.toml") || normalized === "Gemfile" || normalized.endsWith("/Gemfile") || normalized === "go.mod" || normalized.endsWith("/go.mod") || normalized === "Cargo.toml" || normalized.endsWith("/Cargo.toml");
11711
+ }
11712
+ function isDependencyWorkspaceFile(filePath) {
11713
+ const normalized = filePath.replaceAll("\\", "/");
11714
+ return normalized === "pnpm-workspace.yaml" || normalized.endsWith("/pnpm-workspace.yaml") || normalized === "lerna.json" || normalized.endsWith("/lerna.json") || normalized === "turbo.json" || normalized.endsWith("/turbo.json") || normalized === "nx.json" || normalized.endsWith("/nx.json");
11715
+ }
11716
+ function inferPackageManager(repoRoot, manifests, lockfiles) {
11717
+ const rootPackageJson = readPackageJson(repoRoot, "package.json");
11718
+ const packageManager = rootPackageJson?.packageManager;
11719
+ if (typeof packageManager === "string") {
11720
+ if (packageManager.startsWith("pnpm@"))
11721
+ return "pnpm";
11722
+ if (packageManager.startsWith("yarn@"))
11723
+ return "yarn";
11724
+ if (packageManager.startsWith("npm@"))
11725
+ return "npm";
11726
+ if (packageManager.startsWith("bun@"))
11727
+ return "bun";
11728
+ }
11729
+ if (lockfiles.some((file) => file.endsWith("pnpm-lock.yaml")))
11730
+ return "pnpm";
11731
+ if (lockfiles.some((file) => file.endsWith("yarn.lock")))
11732
+ return "yarn";
11733
+ if (lockfiles.some((file) => file.endsWith("package-lock.json"))) {
11734
+ return "npm";
11735
+ }
11736
+ if (lockfiles.some((file) => file.endsWith("bun.lock")))
11737
+ return "bun";
11738
+ if (manifests.some((file) => file.endsWith("requirements.txt") || file.endsWith("pyproject.toml") || file.endsWith("Pipfile"))) {
11739
+ return "pip";
11740
+ }
11741
+ if (manifests.some((file) => file.endsWith("Gemfile")))
11742
+ return "bundler";
11743
+ if (manifests.some((file) => file.endsWith("go.mod")))
11744
+ return "go";
11745
+ if (manifests.some((file) => file.endsWith("Cargo.toml")))
11746
+ return "cargo";
11747
+ return "unknown";
11748
+ }
11749
+ function packageSignalsFor(repoRoot, input) {
11750
+ const manifestDeps = dependencyNamesByManifest(repoRoot, input.manifests);
11751
+ const explicitPackageNames = unique([
11752
+ ...packagesMentionedInText(input.task),
11753
+ ...packagesMentionedInDiff(input.diff ?? "")
11754
+ ]).filter((name) => !looksLikeVersionOrAdvisory(name));
11755
+ const fallbackPackageNames = input.changedDependencyFiles.flatMap((file) => packagesFromChangedDependencyFile(repoRoot, file));
11756
+ const packageNames = unique(explicitPackageNames.length > 0 ? explicitPackageNames : fallbackPackageNames).filter((name) => !looksLikeVersionOrAdvisory(name));
11757
+ return packageNames.slice(0, 16).map((name) => ({
11758
+ name,
11759
+ changeKind: changeKindFor(name, input.diff ?? ""),
11760
+ manifestPaths: manifestsForPackage(name, manifestDeps),
11761
+ lockfilePaths: input.changedDependencyFiles.filter(isDependencyLockfile),
11762
+ evidence: evidenceForPackage(name, input.task, input.diff ?? "")
11763
+ }));
11764
+ }
11765
+ function dependencyNamesByManifest(repoRoot, manifests) {
11766
+ const byPackage = /* @__PURE__ */ new Map();
11767
+ for (const manifest of manifests) {
11768
+ if (!manifest.endsWith("package.json"))
11769
+ continue;
11770
+ const parsed = readPackageJson(repoRoot, manifest);
11771
+ if (!parsed)
11772
+ continue;
11773
+ for (const name of packageJsonDependencyNames(parsed)) {
11774
+ const current = byPackage.get(name) ?? [];
11775
+ current.push(manifest);
11776
+ byPackage.set(name, current);
11777
+ }
11778
+ }
11779
+ return byPackage;
11780
+ }
11781
+ function packageJsonDependencyNames(parsed) {
11782
+ return unique([
11783
+ "dependencies",
11784
+ "devDependencies",
11785
+ "optionalDependencies",
11786
+ "peerDependencies",
11787
+ "overrides",
11788
+ "resolutions",
11789
+ "pnpm"
11790
+ ].flatMap((field) => nestedDependencyNames(parsed[field])));
11791
+ }
11792
+ function nestedDependencyNames(value) {
11793
+ if (!value || typeof value !== "object")
11794
+ return [];
11795
+ return Object.entries(value).flatMap(([key, nested]) => [
11796
+ looksLikePackageName(key) ? key : "",
11797
+ ...nestedDependencyNames(nested)
11798
+ ]);
11799
+ }
11800
+ function packagesMentionedInText(text) {
11801
+ const names = [];
11802
+ const quoted = text.matchAll(/[`'"](@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)[`'"]/gi);
11803
+ for (const match of quoted)
11804
+ names.push(match[1] ?? "");
11805
+ const advisoryContext = text.matchAll(/\b(?:package|dependency|upgrade|update|override|transitive|vulnerable|dependabot|cve|ghsa|audit)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
11806
+ for (const match of advisoryContext)
11807
+ names.push(match[1] ?? "");
11808
+ const atVersion = text.matchAll(/\b(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/gi);
11809
+ for (const match of atVersion)
11810
+ names.push(match[1] ?? "");
11811
+ if (/\b(dependabot|cve-|ghsa-|audit|vulnerab|advisory)\b/i.test(text)) {
11812
+ const affectedPackageContext = text.matchAll(/\b(?:for|affects?|in)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
11813
+ for (const match of affectedPackageContext)
11814
+ names.push(match[1] ?? "");
11815
+ }
11816
+ return unique(names.map(cleanPackageName).filter(looksLikePackageName).filter((name) => !genericDependencyWords.has(name.toLowerCase())));
11817
+ }
11818
+ function packagesMentionedInDiff(diff) {
11819
+ const names = [];
11820
+ for (const line of diff.split(/\r?\n/)) {
11821
+ if (!/^[+-]/.test(line) || /^[+-]{3}/.test(line))
11822
+ continue;
11823
+ const packageJsonMatch = line.match(/"(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)"\s*:/i);
11824
+ if (packageJsonMatch?.[1])
11825
+ names.push(packageJsonMatch[1]);
11826
+ const pnpmMatch = line.match(/^\s*[+-]\s{0,8}(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
11827
+ if (pnpmMatch?.[1])
11828
+ names.push(pnpmMatch[1]);
11829
+ const lockPathMatch = line.match(/\/(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
11830
+ if (lockPathMatch?.[1])
11831
+ names.push(lockPathMatch[1]);
11832
+ }
11833
+ return unique(names.map(cleanPackageName).filter(looksLikePackageName).filter((name) => !genericDependencyWords.has(name.toLowerCase())));
11834
+ }
11835
+ function packagesFromChangedDependencyFile(repoRoot, filePath) {
11836
+ if (!filePath.endsWith("package.json"))
11837
+ return [];
11838
+ const parsed = readPackageJson(repoRoot, filePath);
11839
+ return parsed ? packageJsonDependencyNames(parsed).slice(0, 20) : [];
11840
+ }
11841
+ function readPackageJson(repoRoot, filePath) {
11842
+ const content = safeRead(path6.join(repoRoot, filePath), 4e5);
11843
+ if (!content)
11844
+ return null;
11845
+ try {
11846
+ const parsed = JSON.parse(content);
11847
+ return parsed && typeof parsed === "object" ? parsed : null;
11848
+ } catch {
11849
+ return null;
11850
+ }
11851
+ }
11852
+ function changeKindFor(packageName, diff) {
11853
+ const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11854
+ const removedVersions = [
11855
+ ...diff.matchAll(new RegExp(`^-.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
11856
+ ].map((match) => match[1] ?? "");
11857
+ const addedVersions = [
11858
+ ...diff.matchAll(new RegExp(`^\\+.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
11859
+ ].map((match) => match[1] ?? "");
11860
+ const removed = removedVersions[0];
11861
+ const added = addedVersions[0];
11862
+ if (removed && added) {
11863
+ const [removedMajor] = removed.split(".");
11864
+ const [addedMajor] = added.split(".");
11865
+ return removedMajor && addedMajor && removedMajor !== addedMajor ? "major" : "patch_or_minor";
11866
+ }
11867
+ if (added && !removed)
11868
+ return "added_or_overridden";
11869
+ return "unknown";
11870
+ }
11871
+ function manifestsForPackage(packageName, manifestDeps) {
11872
+ return unique([
11873
+ ...manifestDeps.get(packageName) ?? [],
11874
+ ...[...manifestDeps.entries()].filter(([name]) => name.endsWith(`/${packageName}`)).flatMap(([, manifests]) => manifests)
11875
+ ]).slice(0, 8);
11876
+ }
11877
+ function evidenceForPackage(packageName, task, diff) {
11878
+ const evidence = [];
11879
+ if (task.toLowerCase().includes(packageName.toLowerCase())) {
11880
+ evidence.push("mentioned_in_task");
11881
+ }
11882
+ if (diff.toLowerCase().includes(packageName.toLowerCase())) {
11883
+ evidence.push("mentioned_in_diff");
11884
+ }
11885
+ if (evidence.length === 0)
11886
+ evidence.push("manifest_candidate");
11887
+ return evidence;
11888
+ }
11889
+ function validationCommandsFor2(repoRoot, packageManager) {
11890
+ const scripts = packageScripts(repoRoot);
11891
+ const run = packageRunCommand(packageManager);
11892
+ return unique([
11893
+ ...auditCommandsFor(packageManager),
11894
+ ...scriptCommand(scripts, "check", run),
11895
+ ...scriptCommand(scripts, "test", run),
11896
+ ...scriptCommand(scripts, "test:client", run),
11897
+ ...scriptCommand(scripts, "test:e2e:smoke", run),
11898
+ ...scriptCommand(scripts, "build", run)
11899
+ ]).slice(0, 10);
11900
+ }
11901
+ function packageScripts(repoRoot) {
11902
+ const parsed = readPackageJson(repoRoot, "package.json");
11903
+ const scripts = parsed?.scripts;
11904
+ return scripts && typeof scripts === "object" ? Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string")) : {};
11905
+ }
11906
+ function scriptCommand(scripts, scriptName, runCommand) {
11907
+ return scripts[scriptName] ? [`${runCommand} ${scriptName}`] : [];
11908
+ }
11909
+ function packageRunCommand(packageManager) {
11910
+ switch (packageManager) {
11911
+ case "npm":
11912
+ return "npm run";
11913
+ case "yarn":
11914
+ return "yarn";
11915
+ case "bun":
11916
+ return "bun run";
11917
+ default:
11918
+ return "pnpm";
11919
+ }
11920
+ }
11921
+ function auditCommandsFor(packageManager) {
11922
+ switch (packageManager) {
11923
+ case "pnpm":
11924
+ return ["pnpm audit --prod"];
11925
+ case "npm":
11926
+ return ["npm audit --omit=dev"];
11927
+ case "yarn":
11928
+ return ["yarn npm audit --environment production"];
11929
+ case "bun":
11930
+ return ["bun audit"];
11931
+ case "pip":
11932
+ return ["pip-audit"];
11933
+ case "bundler":
11934
+ return ["bundle audit check"];
11935
+ case "go":
11936
+ return ["govulncheck ./..."];
11937
+ case "cargo":
11938
+ return ["cargo audit"];
11939
+ default:
11940
+ return [];
11941
+ }
11942
+ }
11943
+ function dependencyGraphCommandsFor(packageManager, packageNames) {
11944
+ const selected = packageNames.slice(0, 5);
11945
+ if (selected.length === 0)
11946
+ return [];
11947
+ switch (packageManager) {
11948
+ case "pnpm":
11949
+ return selected.map((name) => `pnpm why ${name}`);
11950
+ case "npm":
11951
+ return selected.map((name) => `npm explain ${name}`);
11952
+ case "yarn":
11953
+ return selected.map((name) => `yarn why ${name}`);
11954
+ case "bun":
11955
+ return selected.map((name) => `bun pm why ${name}`);
11956
+ case "go":
11957
+ return selected.map((name) => `go mod why -m ${name}`);
11958
+ case "cargo":
11959
+ return selected.map((name) => `cargo tree -i ${name}`);
11960
+ default:
11961
+ return [];
11962
+ }
11963
+ }
11964
+ function remediationCommandsFor(packageManager, packageNames) {
11965
+ const selected = packageNames.slice(0, 5);
11966
+ if (selected.length === 0)
11967
+ return [];
11968
+ switch (packageManager) {
11969
+ case "pnpm":
11970
+ return selected.map((name) => `pnpm up ${name} --latest --interactive=false`);
11971
+ case "npm":
11972
+ return selected.map((name) => `npm update ${name}`);
11973
+ case "yarn":
11974
+ return selected.map((name) => `yarn up ${name}`);
11975
+ case "bun":
11976
+ return selected.map((name) => `bun update ${name}`);
11977
+ case "go":
11978
+ return selected.map((name) => `go get ${name}@latest`);
11979
+ case "cargo":
11980
+ return selected.map((name) => `cargo update -p ${name}`);
11981
+ default:
11982
+ return [];
11983
+ }
11984
+ }
11985
+ function residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles) {
11986
+ return unique([
11987
+ packageNames.length > 0 ? `Confirm vulnerable versions are gone for: ${packageNames.slice(0, 8).join(", ")}.` : "Name each advisory/package from the scanner before editing.",
11988
+ changedDependencyFiles.some(isDependencyLockfile) ? "Inspect the lockfile diff for unexpected package churn outside the advisory path." : "Regenerate and inspect the lockfile if the fix changes dependency resolution.",
11989
+ "Split major-version upgrades or broad framework upgrades into a separate mission unless the advisory requires them.",
11990
+ "Record any remaining advisory ids, ignored dev-only findings, or transitive paths that still need owner judgment.",
11991
+ ...auditCommandsFor(packageManager).map((command2) => `Rerun ${command2} after the lockfile update.`)
11992
+ ]);
11993
+ }
11994
+ function confidenceFor(input) {
11995
+ let score = 35;
11996
+ if (input.changedDependencyFiles.length > 0)
11997
+ score += 20;
11998
+ if (input.packageSignals.length > 0)
11999
+ score += 20;
12000
+ if (input.lockfiles.length > 0)
12001
+ score += 10;
12002
+ if (input.validationCommands.length > 0)
12003
+ score += 10;
12004
+ return Math.min(95, score);
12005
+ }
12006
+ function summaryFor3(input) {
12007
+ const packages = input.packageSignals.map((signal2) => signal2.name);
12008
+ return [
12009
+ `Detected ${input.packageManager} dependency remediation context.`,
12010
+ `${input.manifests.length} manifest${input.manifests.length === 1 ? "" : "s"}, ${input.lockfiles.length} lockfile${input.lockfiles.length === 1 ? "" : "s"}.`,
12011
+ input.changedDependencyFiles.length > 0 ? `Changed dependency files: ${input.changedDependencyFiles.slice(0, 6).join(", ")}.` : "No dependency files are changed yet.",
12012
+ packages.length > 0 ? `Likely packages: ${packages.slice(0, 8).join(", ")}.` : "Package names still need advisory/scanner evidence.",
12013
+ input.validationCommands.length > 0 ? `Validation starts with ${input.validationCommands[0]}.` : "Validation commands need repo-specific confirmation."
12014
+ ].join(" ");
12015
+ }
12016
+ function cleanPackageName(input) {
12017
+ return input.replace(/[),.;\]]+$/g, "").trim();
12018
+ }
12019
+ function looksLikePackageName(input) {
12020
+ return /^@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?$/i.test(input);
12021
+ }
12022
+ function looksLikeVersionOrAdvisory(input) {
12023
+ const normalized = input.toLowerCase();
12024
+ return /^\d/.test(input) || /^cve-\d/i.test(input) || /^ghsa-/i.test(input) || [
12025
+ "advisory",
12026
+ "advisories",
12027
+ "change",
12028
+ "changes",
12029
+ "dependency",
12030
+ "dependencies",
12031
+ "dependabot",
12032
+ "finding",
12033
+ "findings",
12034
+ "package",
12035
+ "packages",
12036
+ "security",
12037
+ "transitive",
12038
+ "upgrade",
12039
+ "upgrades",
12040
+ "vulnerabilities",
12041
+ "vulnerability",
12042
+ "vulnerable"
12043
+ ].includes(normalized);
12044
+ }
12045
+ var dependencyFilePatterns, genericDependencyWords;
12046
+ var init_dependencyRemediation = __esm({
12047
+ "../core/dist/dependencyRemediation.js"() {
12048
+ "use strict";
12049
+ init_utils();
12050
+ dependencyFilePatterns = [
12051
+ "package.json",
12052
+ "pnpm-lock.yaml",
12053
+ "pnpm-workspace.yaml",
12054
+ "package-lock.json",
12055
+ "yarn.lock",
12056
+ "bun.lock",
12057
+ "npm-shrinkwrap.json",
12058
+ "requirements.txt",
12059
+ "poetry.lock",
12060
+ "pyproject.toml",
12061
+ "Pipfile.lock",
12062
+ "Gemfile",
12063
+ "Gemfile.lock",
12064
+ "go.mod",
12065
+ "go.sum",
12066
+ "Cargo.toml",
12067
+ "Cargo.lock"
12068
+ ];
12069
+ genericDependencyWords = /* @__PURE__ */ new Set([
12070
+ "advisory",
12071
+ "audit",
12072
+ "build",
12073
+ "change",
12074
+ "dependency",
12075
+ "dev",
12076
+ "exact",
12077
+ "existing",
12078
+ "focused",
12079
+ "for",
12080
+ "finding",
12081
+ "lockfile",
12082
+ "package",
12083
+ "path",
12084
+ "security",
12085
+ "test",
12086
+ "the",
12087
+ "toolchain",
12088
+ "transitive",
12089
+ "vulnerability",
12090
+ "workflow",
12091
+ "without"
12092
+ ]);
12093
+ }
12094
+ });
12095
+
11538
12096
  // ../core/dist/testRecommendations.js
11539
12097
  import { existsSync as existsSync3 } from "node:fs";
11540
- import path6 from "node:path";
12098
+ import path7 from "node:path";
11541
12099
  function recommendExactTestsForFiles(input) {
11542
12100
  const packageManager = detectPackageManager(input.repoRoot);
11543
12101
  if (isDocsOnlyChange(input.changedFiles)) {
@@ -11691,7 +12249,7 @@ function taskRelevantTests(repoRoot, scanMap, scanSources, task, excluded) {
11691
12249
  }));
11692
12250
  return [...mapTests, ...sourceTests].filter((test) => !excludedSet.has(test.path)).map((test) => {
11693
12251
  const pathScore = tokens.reduce((total, token) => total + (test.seed.includes(token) ? 2 : 0), 0);
11694
- const content = pathScore > 0 ? (safeRead(path6.join(repoRoot, test.path), 6e4) ?? "").toLowerCase() : "";
12252
+ const content = pathScore > 0 ? (safeRead(path7.join(repoRoot, test.path), 6e4) ?? "").toLowerCase() : "";
11695
12253
  return {
11696
12254
  path: test.path,
11697
12255
  score: pathScore + tokens.reduce((total, token) => total + (test.context.includes(token) ? 1 : 0), 0) + tokens.reduce((total, token) => total + (content.includes(token) ? 1 : 0), 0)
@@ -11755,8 +12313,8 @@ function testCommandForFile(repoRoot, file, packageManager) {
11755
12313
  if (packageManager === "pnpm") {
11756
12314
  const packageInfo = nearestPackageInfo(repoRoot, file);
11757
12315
  if (packageInfo?.name && packageInfo.root !== repoRoot) {
11758
- const packageRelativeFile = path6.relative(packageInfo.root, path6.join(repoRoot, file)).replaceAll("\\", "/");
11759
- const testScript = readPackageJson(packageInfo.root)?.scripts?.test;
12316
+ const packageRelativeFile = path7.relative(packageInfo.root, path7.join(repoRoot, file)).replaceAll("\\", "/");
12317
+ const testScript = readPackageJson2(packageInfo.root)?.scripts?.test;
11760
12318
  if (typeof testScript === "string") {
11761
12319
  if (/\bvitest\b/.test(testScript)) {
11762
12320
  return `pnpm --filter ${packageInfo.name} exec vitest run ${packageRelativeFile}`;
@@ -11774,11 +12332,11 @@ function testCommandForFile(repoRoot, file, packageManager) {
11774
12332
  return `${packageManager} test -- ${file}`;
11775
12333
  }
11776
12334
  function nearestPackageInfo(repoRoot, file) {
11777
- let current = path6.dirname(path6.join(repoRoot, file));
12335
+ let current = path7.dirname(path7.join(repoRoot, file));
11778
12336
  while (current.startsWith(repoRoot)) {
11779
- const packageJsonPath = path6.join(current, "package.json");
12337
+ const packageJsonPath = path7.join(current, "package.json");
11780
12338
  if (existsSync3(packageJsonPath)) {
11781
- const pkg = readPackageJson(current);
12339
+ const pkg = readPackageJson2(current);
11782
12340
  return {
11783
12341
  root: current,
11784
12342
  ...typeof pkg?.name === "string" ? { name: pkg.name } : {}
@@ -11786,18 +12344,18 @@ function nearestPackageInfo(repoRoot, file) {
11786
12344
  }
11787
12345
  if (current === repoRoot)
11788
12346
  break;
11789
- current = path6.dirname(current);
12347
+ current = path7.dirname(current);
11790
12348
  }
11791
12349
  return null;
11792
12350
  }
11793
12351
  function coLocatedTestCandidates(repoRoot, file) {
11794
- const parsed = path6.parse(file);
12352
+ const parsed = path7.parse(file);
11795
12353
  const extensions = parsed.ext === ".tsx" ? [".test.tsx", ".spec.tsx", ".test.ts", ".spec.ts"] : parsed.ext === ".jsx" ? [".test.jsx", ".spec.jsx", ".test.js", ".spec.js"] : [`.test${parsed.ext}`, `.spec${parsed.ext}`];
11796
12354
  const candidates = [
11797
- ...extensions.map((extension) => path6.join(parsed.dir, `${parsed.name}${extension}`)),
11798
- ...extensions.map((extension) => path6.join(parsed.dir, "__tests__", `${parsed.name}${extension}`))
12355
+ ...extensions.map((extension) => path7.join(parsed.dir, `${parsed.name}${extension}`)),
12356
+ ...extensions.map((extension) => path7.join(parsed.dir, "__tests__", `${parsed.name}${extension}`))
11799
12357
  ].map((candidate) => candidate.replaceAll("\\", "/"));
11800
- return candidates.filter((candidate) => existsSync3(path6.join(repoRoot, candidate)));
12358
+ return candidates.filter((candidate) => existsSync3(path7.join(repoRoot, candidate)));
11801
12359
  }
11802
12360
  function importedByTests(scanMap, sourceFiles) {
11803
12361
  const fromSourceMetadata = scanMap.filter((node) => sourceFiles.includes(sourcePathForNode(node))).flatMap((node) => metadataStrings3(node, "importedBy")).filter(isTestPath);
@@ -11819,11 +12377,11 @@ function isDocsOnlyChange(files) {
11819
12377
  }
11820
12378
  function isDocsOrProductDocPath(file) {
11821
12379
  const normalized = file.replaceAll("\\", "/");
11822
- const basename = path6.basename(normalized).toLowerCase();
12380
+ const basename = path7.basename(normalized).toLowerCase();
11823
12381
  return normalized.startsWith("docs/") || normalized.includes("/docs/") || normalized.startsWith(".github/") || basename === "readme.md" || basename === "agents.md" || basename === "claude.md" || basename.endsWith(".md") || basename.endsWith(".mdx") || basename.endsWith(".rst") || basename.endsWith(".adoc");
11824
12382
  }
11825
12383
  function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
11826
- const pkg = readPackageJson(repoRoot);
12384
+ const pkg = readPackageJson2(repoRoot);
11827
12385
  const scripts = pkg?.scripts ?? {};
11828
12386
  const recommendations = [
11829
12387
  {
@@ -11847,7 +12405,7 @@ function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
11847
12405
  if (typeof scripts[script] === "string") {
11848
12406
  recommendations.push({
11849
12407
  command: `${packageManager} ${script}`,
11850
- reason: `Repo package.json defines ${script}; run it when docs or agent guidance can affect generated output or published docs.`,
12408
+ reason: `Repo package.json defines ${script}; run it when docs or agent guidance can affect generated output or shared docs.`,
11851
12409
  files: [],
11852
12410
  source: "script",
11853
12411
  confidence: script === "check" ? "low" : "medium"
@@ -11857,7 +12415,7 @@ function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
11857
12415
  return recommendations.slice(0, 4);
11858
12416
  }
11859
12417
  function packageScriptRecommendations(repoRoot, packageManager) {
11860
- const pkg = readPackageJson(repoRoot);
12418
+ const pkg = readPackageJson2(repoRoot);
11861
12419
  const scripts = pkg?.scripts ?? {};
11862
12420
  const commands = [];
11863
12421
  for (const script of [
@@ -11890,7 +12448,7 @@ function taskRelevantPackageScriptRecommendations(repoRoot, packageManager, task
11890
12448
  const packageRoots = unique(sourceFiles.map((file) => nearestPackageInfo(repoRoot, file)?.root).filter((root) => Boolean(root)));
11891
12449
  const recommendations = [];
11892
12450
  for (const packageRoot of packageRoots) {
11893
- const pkg = readPackageJson(packageRoot);
12451
+ const pkg = readPackageJson2(packageRoot);
11894
12452
  const scripts = pkg?.scripts ?? {};
11895
12453
  const packageName = typeof pkg?.name === "string" ? pkg.name : void 0;
11896
12454
  if (wantsPackageRelease) {
@@ -11923,8 +12481,8 @@ function packageScriptCommand(packageManager, packageRoot, repoRoot, packageName
11923
12481
  }
11924
12482
  return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
11925
12483
  }
11926
- function readPackageJson(packageRoot) {
11927
- const content = safeRead(path6.join(packageRoot, "package.json"), 2e5);
12484
+ function readPackageJson2(packageRoot) {
12485
+ const content = safeRead(path7.join(packageRoot, "package.json"), 2e5);
11928
12486
  if (!content)
11929
12487
  return null;
11930
12488
  try {
@@ -11940,17 +12498,17 @@ function shellQuote(value) {
11940
12498
  return `'${value.replaceAll("'", `'"'"'`)}'`;
11941
12499
  }
11942
12500
  function detectPackageManager(repoRoot) {
11943
- const pkg = readPackageJson(repoRoot);
12501
+ const pkg = readPackageJson2(repoRoot);
11944
12502
  if (typeof pkg?.packageManager === "string") {
11945
12503
  const name = pkg.packageManager.split("@")[0];
11946
12504
  if (name)
11947
12505
  return name;
11948
12506
  }
11949
- if (existsSync3(path6.join(repoRoot, "pnpm-lock.yaml")))
12507
+ if (existsSync3(path7.join(repoRoot, "pnpm-lock.yaml")))
11950
12508
  return "pnpm";
11951
- if (existsSync3(path6.join(repoRoot, "yarn.lock")))
12509
+ if (existsSync3(path7.join(repoRoot, "yarn.lock")))
11952
12510
  return "yarn";
11953
- if (existsSync3(path6.join(repoRoot, "bun.lockb")))
12511
+ if (existsSync3(path7.join(repoRoot, "bun.lockb")))
11954
12512
  return "bun";
11955
12513
  return "npm";
11956
12514
  }
@@ -11967,6 +12525,10 @@ function contextForTask(scan, request) {
11967
12525
  const files = request.files ?? [];
11968
12526
  const maxTokens = request.maxTokens ?? 6e3;
11969
12527
  const scoped = files.length > 0;
12528
+ const dependencyRemediation = isDependencyRemediationTask(task, files) ? buildDependencyRemediationPlan(request.repoRoot ?? scan.repoRoot, {
12529
+ task,
12530
+ changedFiles: files
12531
+ }) : void 0;
11970
12532
  const rankedRules = rankRules(scan.rules, task, files);
11971
12533
  const initialRules = rankedRules.slice(0, scoped ? 8 : 12);
11972
12534
  const initialSkills = selectSkills(scan.skills, task, files, scoped).map(compactSkillForContext);
@@ -11974,13 +12536,18 @@ function contextForTask(scan, request) {
11974
12536
  const rankedMap = rankMap(mapCandidates, task, files);
11975
12537
  const selectedMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(initialRules, task), initialSkills, rankedMap, files, scoped ? 14 : 18).map(compactMapNodeForContext);
11976
12538
  const suggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, selectedMap, request.repoRoot);
12539
+ const dependencyTests = dependencyValidationForContext(dependencyRemediation, task);
12540
+ const focusedSuggestedTests = dependencyRemediation ? dependencyTests : suggestedTests;
11977
12541
  const initialSelection = {
11978
12542
  rules: initialRules,
11979
12543
  skills: initialSkills,
11980
12544
  map: selectedMap,
11981
- wikiPages: selectWikiPages(buildContextWiki(scan).pages, task, files, scoped, suggestedTests).map(compactWikiPageForContext),
12545
+ wikiPages: selectWikiPages(buildContextWiki(scan).pages, task, files, scoped, focusedSuggestedTests).map(compactWikiPageForContext),
11982
12546
  memories: rankMemories(scan.memories, task, files).slice(0, scoped ? 4 : 8).map(compactMemoryForContext),
11983
- suggestedTests
12547
+ suggestedTests: unique([...dependencyTests, ...focusedSuggestedTests]),
12548
+ ...dependencyRemediation ? {
12549
+ dependencyRemediation: compactDependencyRemediationForContext(dependencyRemediation)
12550
+ } : {}
11984
12551
  };
11985
12552
  const { selection, tokenEstimate, omitted } = fitSelectionToBudget(initialSelection, {
11986
12553
  maxTokens,
@@ -11991,32 +12558,39 @@ function contextForTask(scan, request) {
11991
12558
  const { rules: selectedRules, skills: selectedSkills, map: budgetedMap, wikiPages: selectedWikiPages, memories: selectedMemories } = selection;
11992
12559
  const finalMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(selectedRules, task), selectedSkills, budgetedMap, files, Math.max(budgetedMap.length, scoped ? 2 : 4)).map(compactMapNodeForContext);
11993
12560
  const finalSuggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, finalMap, request.repoRoot);
12561
+ const packetSuggestedTests = unique([
12562
+ ...dependencyRemediation ? dependencyValidationForContext(dependencyRemediation, task) : finalSuggestedTests
12563
+ ]);
11994
12564
  const owners = selectOwners(scan.map, files);
11995
12565
  const sources = unique([
11996
12566
  ...selectedRules.flatMap((rule) => rule.sourcePaths),
11997
12567
  ...selectedSkills.map((skill) => skill.sourcePath),
11998
12568
  ...finalMap.map(mapNodeSourcePath),
11999
12569
  ...selectedWikiPages.flatMap((page2) => page2.sourcePaths),
12000
- ...selectedMemories.flatMap((memory) => memory.sourcePaths)
12570
+ ...selectedMemories.flatMap((memory) => memory.sourcePaths),
12571
+ ...dependencySourcePaths(dependencyRemediation)
12001
12572
  ]);
12002
12573
  const teamGuidance = scan.teamGuidanceCatalog ? teamGuidanceDeliveryFor(scan, selectedRules, selectedSkills, selectedMemories) : void 0;
12003
12574
  const packet = {
12004
12575
  packetId: packetId(),
12005
12576
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12006
12577
  task,
12007
- summary: summarizeContext(task, selectedRules, finalSuggestedTests, finalMap),
12578
+ summary: summarizeContext(task, selectedRules, packetSuggestedTests, finalMap),
12008
12579
  rules: selectedRules,
12009
12580
  skills: selectedSkills,
12010
12581
  map: finalMap,
12011
12582
  wikiPages: selectedWikiPages,
12012
12583
  memories: selectedMemories,
12013
- suggestedTests: finalSuggestedTests,
12584
+ suggestedTests: packetSuggestedTests,
12014
12585
  owners,
12015
12586
  sources,
12016
12587
  tokenEstimate,
12017
12588
  omitted,
12018
- valueSignals: contextValueSignals(finalMap, finalSuggestedTests, sources.length, teamGuidance?.applied.length ?? 0),
12019
- ...teamGuidance ? { teamGuidance } : {}
12589
+ valueSignals: contextValueSignals(finalMap, packetSuggestedTests, sources.length, teamGuidance?.applied.length ?? 0),
12590
+ ...teamGuidance ? { teamGuidance } : {},
12591
+ ...dependencyRemediation ? {
12592
+ dependencyRemediation: compactDependencyRemediationForContext(dependencyRemediation)
12593
+ } : {}
12020
12594
  };
12021
12595
  return enforceContextPacketBudget(packet, maxTokens, files);
12022
12596
  }
@@ -12050,6 +12624,47 @@ function contextValueSignals(map, suggestedTests, relevantSourceCount, appliedTe
12050
12624
  suggestedTestCount: suggestedTests.length
12051
12625
  };
12052
12626
  }
12627
+ function dependencyValidationForContext(plan, task) {
12628
+ if (!plan)
12629
+ return [];
12630
+ const allowsRuntimeProof = /\b(runtime|smoke|e2e|browser|production|integration)\b/i.test(task);
12631
+ return plan.validationCommands.filter((command2) => allowsRuntimeProof ? true : !/\b(e2e|smoke|ux)\b/i.test(command2)).slice(0, allowsRuntimeProof ? 5 : 4);
12632
+ }
12633
+ function dependencySourcePaths(plan) {
12634
+ if (!plan)
12635
+ return [];
12636
+ return unique([
12637
+ ...plan.manifestFiles,
12638
+ ...plan.lockfiles,
12639
+ ...plan.workspaceFiles,
12640
+ ...plan.changedDependencyFiles,
12641
+ ...plan.directDependencyFiles
12642
+ ]);
12643
+ }
12644
+ function compactDependencyRemediationForContext(plan) {
12645
+ if (!plan)
12646
+ return void 0;
12647
+ return {
12648
+ ...plan,
12649
+ manifestFiles: plan.manifestFiles.slice(0, 8),
12650
+ lockfiles: plan.lockfiles.slice(0, 6),
12651
+ workspaceFiles: plan.workspaceFiles.slice(0, 4),
12652
+ changedDependencyFiles: plan.changedDependencyFiles.slice(0, 8),
12653
+ directDependencyFiles: plan.directDependencyFiles.slice(0, 8),
12654
+ packageSignals: plan.packageSignals.slice(0, 8).map((signal2) => ({
12655
+ ...signal2,
12656
+ manifestPaths: signal2.manifestPaths.slice(0, 4),
12657
+ lockfilePaths: signal2.lockfilePaths.slice(0, 4),
12658
+ evidence: signal2.evidence.slice(0, 3)
12659
+ })),
12660
+ graphCommands: plan.graphCommands.slice(0, 6),
12661
+ remediationCommands: plan.remediationCommands.slice(0, 6),
12662
+ validationCommands: plan.validationCommands.slice(0, 8),
12663
+ residualRiskChecks: plan.residualRiskChecks.slice(0, 6),
12664
+ handoffRequirements: plan.handoffRequirements.slice(0, 4),
12665
+ summary: truncateText(plan.summary, 520)
12666
+ };
12667
+ }
12053
12668
  function codebaseMapForTask(scan, request, limit = 80) {
12054
12669
  const files = request.files ?? [];
12055
12670
  const hasSpecificScope = files.length > 0 || wordsFor3(request.task).length > 1;
@@ -12927,7 +13542,8 @@ function cloneSelection(selection) {
12927
13542
  map: [...selection.map],
12928
13543
  wikiPages: [...selection.wikiPages],
12929
13544
  memories: [...selection.memories],
12930
- suggestedTests: [...selection.suggestedTests]
13545
+ suggestedTests: [...selection.suggestedTests],
13546
+ ...selection.dependencyRemediation ? { dependencyRemediation: selection.dependencyRemediation } : {}
12931
13547
  };
12932
13548
  }
12933
13549
  function estimateSelectionTokens(selection, task) {
@@ -12942,6 +13558,7 @@ function estimateSelectionTokens(selection, task) {
12942
13558
  wikiPages: selection.wikiPages,
12943
13559
  memories: selection.memories,
12944
13560
  suggestedTests: selection.suggestedTests,
13561
+ ...selection.dependencyRemediation ? { dependencyRemediation: selection.dependencyRemediation } : {},
12945
13562
  owners: [],
12946
13563
  sources: selectionSources(selection),
12947
13564
  tokenEstimate: 0,
@@ -13038,10 +13655,6 @@ function trimContextPacket(packet, protectedMapPaths) {
13038
13655
  packet.suggestedTests.pop();
13039
13656
  return true;
13040
13657
  }
13041
- if (packet.rules.length > 0) {
13042
- packet.rules.pop();
13043
- return true;
13044
- }
13045
13658
  if (packet.map.length > protectedMapPaths.size) {
13046
13659
  packet.map.pop();
13047
13660
  return true;
@@ -13136,7 +13749,8 @@ function refreshContextPacketDerivedFields(packet) {
13136
13749
  map: packet.map,
13137
13750
  wikiPages: packet.wikiPages,
13138
13751
  memories: packet.memories,
13139
- suggestedTests: packet.suggestedTests
13752
+ suggestedTests: packet.suggestedTests,
13753
+ ...packet.dependencyRemediation ? { dependencyRemediation: packet.dependencyRemediation } : {}
13140
13754
  });
13141
13755
  refreshTeamGuidanceDelivery(packet);
13142
13756
  packet.valueSignals = contextValueSignals(packet.map, packet.suggestedTests, packet.sources.length, packet.teamGuidance?.applied.length ?? 0);
@@ -13188,7 +13802,8 @@ function selectionSources(selection) {
13188
13802
  ...selection.skills.map((skill) => skill.sourcePath),
13189
13803
  ...selection.map.map((node) => node.path),
13190
13804
  ...selection.wikiPages.flatMap((page2) => page2.sourcePaths),
13191
- ...selection.memories.flatMap((memory) => memory.sourcePaths)
13805
+ ...selection.memories.flatMap((memory) => memory.sourcePaths),
13806
+ ...dependencySourcePaths(selection.dependencyRemediation)
13192
13807
  ]);
13193
13808
  }
13194
13809
  function trimSelection(selection, minimums, protectedMapPaths) {
@@ -13333,6 +13948,7 @@ var init_context = __esm({
13333
13948
  init_scanner();
13334
13949
  init_contextWiki();
13335
13950
  init_pathIntent();
13951
+ init_dependencyRemediation();
13336
13952
  init_testRecommendations();
13337
13953
  init_utils();
13338
13954
  contextStopWords = /* @__PURE__ */ new Set([
@@ -13405,443 +14021,6 @@ var init_context = __esm({
13405
14021
  }
13406
14022
  });
13407
14023
 
13408
- // ../core/dist/dependencyRemediation.js
13409
- import path7 from "node:path";
13410
- function buildDependencyRemediationPlan(repoRoot, request) {
13411
- const files = walkFiles(repoRoot, request.maxFiles ?? 6e3);
13412
- const manifests = files.filter(isDependencyManifest);
13413
- const lockfiles = files.filter(isDependencyLockfile);
13414
- const workspaceFiles = files.filter(isDependencyWorkspaceFile);
13415
- const changedDependencyFiles = unique((request.changedFiles ?? []).filter(isDependencyFile));
13416
- const packageManager = inferPackageManager(repoRoot, manifests, lockfiles);
13417
- const packageSignalInput = {
13418
- task: request.task,
13419
- manifests,
13420
- changedDependencyFiles
13421
- };
13422
- if (request.diff)
13423
- packageSignalInput.diff = request.diff;
13424
- const packageSignals = packageSignalsFor(repoRoot, packageSignalInput);
13425
- const directDependencyFiles = unique(packageSignals.flatMap((signal2) => signal2.manifestPaths));
13426
- const packageNames = packageSignals.map((signal2) => signal2.name);
13427
- const validationCommands2 = validationCommandsFor2(repoRoot, packageManager);
13428
- const graphCommands = dependencyGraphCommandsFor(packageManager, packageNames);
13429
- const remediationCommands = remediationCommandsFor(packageManager, packageNames);
13430
- const residualRiskChecks = residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles);
13431
- const confidence = confidenceFor({
13432
- changedDependencyFiles,
13433
- packageSignals,
13434
- lockfiles,
13435
- validationCommands: validationCommands2
13436
- });
13437
- return {
13438
- id: `dep_${contentHash([
13439
- request.task,
13440
- packageManager,
13441
- changedDependencyFiles.join(","),
13442
- packageNames.join(",")
13443
- ].join(":"))}`,
13444
- status: changedDependencyFiles.length > 0 || packageSignals.length > 0 ? "ready" : "needs_evidence",
13445
- packageManager,
13446
- manifestFiles: manifests.slice(0, 20),
13447
- lockfiles,
13448
- workspaceFiles,
13449
- changedDependencyFiles,
13450
- directDependencyFiles,
13451
- packageSignals,
13452
- graphCommands,
13453
- remediationCommands,
13454
- validationCommands: validationCommands2,
13455
- residualRiskChecks,
13456
- handoffRequirements: [
13457
- "List advisory ids or scanner finding ids that were fixed.",
13458
- "Name packages still present in the lockfile with vulnerable versions, or state that the scan/audit is clean.",
13459
- "Explain any major-version upgrades, overrides, peer dependency changes, skipped advisories, or remaining advisory ids.",
13460
- "Attach validation commands and CI follow-up results before marking the security mission done."
13461
- ],
13462
- summary: summaryFor3({
13463
- packageManager,
13464
- manifests,
13465
- lockfiles,
13466
- changedDependencyFiles,
13467
- packageSignals,
13468
- validationCommands: validationCommands2
13469
- }),
13470
- confidence
13471
- };
13472
- }
13473
- function isDependencyFile(filePath) {
13474
- const normalized = filePath.replaceAll("\\", "/");
13475
- return dependencyFilePatterns.some((pattern) => normalized === pattern || normalized.endsWith(`/${pattern}`) || pattern === "package.json" && normalized.endsWith("/package.json"));
13476
- }
13477
- function isDependencyLockfile(filePath) {
13478
- const normalized = filePath.replaceAll("\\", "/");
13479
- return normalized === "pnpm-lock.yaml" || normalized.endsWith("/pnpm-lock.yaml") || normalized === "package-lock.json" || normalized.endsWith("/package-lock.json") || normalized === "yarn.lock" || normalized.endsWith("/yarn.lock") || normalized === "bun.lock" || normalized.endsWith("/bun.lock") || normalized === "npm-shrinkwrap.json" || normalized.endsWith("/npm-shrinkwrap.json") || normalized === "poetry.lock" || normalized.endsWith("/poetry.lock") || normalized === "Pipfile.lock" || normalized.endsWith("/Pipfile.lock") || normalized === "Gemfile.lock" || normalized.endsWith("/Gemfile.lock") || normalized === "go.sum" || normalized.endsWith("/go.sum") || normalized === "Cargo.lock" || normalized.endsWith("/Cargo.lock");
13480
- }
13481
- function isDependencyManifest(filePath) {
13482
- const normalized = filePath.replaceAll("\\", "/");
13483
- return normalized === "package.json" || normalized.endsWith("/package.json") || normalized === "requirements.txt" || normalized.endsWith("/requirements.txt") || normalized === "pyproject.toml" || normalized.endsWith("/pyproject.toml") || normalized === "Gemfile" || normalized.endsWith("/Gemfile") || normalized === "go.mod" || normalized.endsWith("/go.mod") || normalized === "Cargo.toml" || normalized.endsWith("/Cargo.toml");
13484
- }
13485
- function isDependencyWorkspaceFile(filePath) {
13486
- const normalized = filePath.replaceAll("\\", "/");
13487
- return normalized === "pnpm-workspace.yaml" || normalized.endsWith("/pnpm-workspace.yaml") || normalized === "lerna.json" || normalized.endsWith("/lerna.json") || normalized === "turbo.json" || normalized.endsWith("/turbo.json") || normalized === "nx.json" || normalized.endsWith("/nx.json");
13488
- }
13489
- function inferPackageManager(repoRoot, manifests, lockfiles) {
13490
- const rootPackageJson = readPackageJson2(repoRoot, "package.json");
13491
- const packageManager = rootPackageJson?.packageManager;
13492
- if (typeof packageManager === "string") {
13493
- if (packageManager.startsWith("pnpm@"))
13494
- return "pnpm";
13495
- if (packageManager.startsWith("yarn@"))
13496
- return "yarn";
13497
- if (packageManager.startsWith("npm@"))
13498
- return "npm";
13499
- if (packageManager.startsWith("bun@"))
13500
- return "bun";
13501
- }
13502
- if (lockfiles.some((file) => file.endsWith("pnpm-lock.yaml")))
13503
- return "pnpm";
13504
- if (lockfiles.some((file) => file.endsWith("yarn.lock")))
13505
- return "yarn";
13506
- if (lockfiles.some((file) => file.endsWith("package-lock.json"))) {
13507
- return "npm";
13508
- }
13509
- if (lockfiles.some((file) => file.endsWith("bun.lock")))
13510
- return "bun";
13511
- if (manifests.some((file) => file.endsWith("requirements.txt") || file.endsWith("pyproject.toml") || file.endsWith("Pipfile"))) {
13512
- return "pip";
13513
- }
13514
- if (manifests.some((file) => file.endsWith("Gemfile")))
13515
- return "bundler";
13516
- if (manifests.some((file) => file.endsWith("go.mod")))
13517
- return "go";
13518
- if (manifests.some((file) => file.endsWith("Cargo.toml")))
13519
- return "cargo";
13520
- return "unknown";
13521
- }
13522
- function packageSignalsFor(repoRoot, input) {
13523
- const manifestDeps = dependencyNamesByManifest(repoRoot, input.manifests);
13524
- const explicitPackageNames = unique([
13525
- ...packagesMentionedInText(input.task),
13526
- ...packagesMentionedInDiff(input.diff ?? "")
13527
- ]).filter((name) => !looksLikeVersionOrAdvisory(name));
13528
- const fallbackPackageNames = input.changedDependencyFiles.flatMap((file) => packagesFromChangedDependencyFile(repoRoot, file));
13529
- const packageNames = unique(explicitPackageNames.length > 0 ? explicitPackageNames : fallbackPackageNames).filter((name) => !looksLikeVersionOrAdvisory(name));
13530
- return packageNames.slice(0, 16).map((name) => ({
13531
- name,
13532
- changeKind: changeKindFor(name, input.diff ?? ""),
13533
- manifestPaths: manifestsForPackage(name, manifestDeps),
13534
- lockfilePaths: input.changedDependencyFiles.filter(isDependencyLockfile),
13535
- evidence: evidenceForPackage(name, input.task, input.diff ?? "")
13536
- }));
13537
- }
13538
- function dependencyNamesByManifest(repoRoot, manifests) {
13539
- const byPackage = /* @__PURE__ */ new Map();
13540
- for (const manifest of manifests) {
13541
- if (!manifest.endsWith("package.json"))
13542
- continue;
13543
- const parsed = readPackageJson2(repoRoot, manifest);
13544
- if (!parsed)
13545
- continue;
13546
- for (const name of packageJsonDependencyNames(parsed)) {
13547
- const current = byPackage.get(name) ?? [];
13548
- current.push(manifest);
13549
- byPackage.set(name, current);
13550
- }
13551
- }
13552
- return byPackage;
13553
- }
13554
- function packageJsonDependencyNames(parsed) {
13555
- return unique([
13556
- "dependencies",
13557
- "devDependencies",
13558
- "optionalDependencies",
13559
- "peerDependencies",
13560
- "overrides",
13561
- "resolutions",
13562
- "pnpm"
13563
- ].flatMap((field) => nestedDependencyNames(parsed[field])));
13564
- }
13565
- function nestedDependencyNames(value) {
13566
- if (!value || typeof value !== "object")
13567
- return [];
13568
- return Object.entries(value).flatMap(([key, nested]) => [
13569
- looksLikePackageName(key) ? key : "",
13570
- ...nestedDependencyNames(nested)
13571
- ]);
13572
- }
13573
- function packagesMentionedInText(text) {
13574
- const names = [];
13575
- const quoted = text.matchAll(/[`'"](@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)[`'"]/gi);
13576
- for (const match of quoted)
13577
- names.push(match[1] ?? "");
13578
- const advisoryContext = text.matchAll(/\b(?:package|dependency|upgrade|update|override|transitive|vulnerable|dependabot|cve|ghsa|audit)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
13579
- for (const match of advisoryContext)
13580
- names.push(match[1] ?? "");
13581
- const atVersion = text.matchAll(/\b(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/gi);
13582
- for (const match of atVersion)
13583
- names.push(match[1] ?? "");
13584
- if (/\b(dependabot|cve-|ghsa-|audit|vulnerab|advisory)\b/i.test(text)) {
13585
- const affectedPackageContext = text.matchAll(/\b(?:for|affects?|in)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
13586
- for (const match of affectedPackageContext)
13587
- names.push(match[1] ?? "");
13588
- }
13589
- return unique(names.map(cleanPackageName).filter(looksLikePackageName));
13590
- }
13591
- function packagesMentionedInDiff(diff) {
13592
- const names = [];
13593
- for (const line of diff.split(/\r?\n/)) {
13594
- if (!/^[+-]/.test(line) || /^[+-]{3}/.test(line))
13595
- continue;
13596
- const packageJsonMatch = line.match(/"(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)"\s*:/i);
13597
- if (packageJsonMatch?.[1])
13598
- names.push(packageJsonMatch[1]);
13599
- const pnpmMatch = line.match(/^\s*[+-]\s{0,8}(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
13600
- if (pnpmMatch?.[1])
13601
- names.push(pnpmMatch[1]);
13602
- const lockPathMatch = line.match(/\/(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
13603
- if (lockPathMatch?.[1])
13604
- names.push(lockPathMatch[1]);
13605
- }
13606
- return unique(names.map(cleanPackageName).filter(looksLikePackageName));
13607
- }
13608
- function packagesFromChangedDependencyFile(repoRoot, filePath) {
13609
- if (!filePath.endsWith("package.json"))
13610
- return [];
13611
- const parsed = readPackageJson2(repoRoot, filePath);
13612
- return parsed ? packageJsonDependencyNames(parsed).slice(0, 20) : [];
13613
- }
13614
- function readPackageJson2(repoRoot, filePath) {
13615
- const content = safeRead(path7.join(repoRoot, filePath), 4e5);
13616
- if (!content)
13617
- return null;
13618
- try {
13619
- const parsed = JSON.parse(content);
13620
- return parsed && typeof parsed === "object" ? parsed : null;
13621
- } catch {
13622
- return null;
13623
- }
13624
- }
13625
- function changeKindFor(packageName, diff) {
13626
- const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13627
- const removedVersions = [
13628
- ...diff.matchAll(new RegExp(`^-.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
13629
- ].map((match) => match[1] ?? "");
13630
- const addedVersions = [
13631
- ...diff.matchAll(new RegExp(`^\\+.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
13632
- ].map((match) => match[1] ?? "");
13633
- const removed = removedVersions[0];
13634
- const added = addedVersions[0];
13635
- if (removed && added) {
13636
- const [removedMajor] = removed.split(".");
13637
- const [addedMajor] = added.split(".");
13638
- return removedMajor && addedMajor && removedMajor !== addedMajor ? "major" : "patch_or_minor";
13639
- }
13640
- if (added && !removed)
13641
- return "added_or_overridden";
13642
- return "unknown";
13643
- }
13644
- function manifestsForPackage(packageName, manifestDeps) {
13645
- return unique([
13646
- ...manifestDeps.get(packageName) ?? [],
13647
- ...[...manifestDeps.entries()].filter(([name]) => name.endsWith(`/${packageName}`)).flatMap(([, manifests]) => manifests)
13648
- ]).slice(0, 8);
13649
- }
13650
- function evidenceForPackage(packageName, task, diff) {
13651
- const evidence = [];
13652
- if (task.toLowerCase().includes(packageName.toLowerCase())) {
13653
- evidence.push("mentioned_in_task");
13654
- }
13655
- if (diff.toLowerCase().includes(packageName.toLowerCase())) {
13656
- evidence.push("mentioned_in_diff");
13657
- }
13658
- if (evidence.length === 0)
13659
- evidence.push("manifest_candidate");
13660
- return evidence;
13661
- }
13662
- function validationCommandsFor2(repoRoot, packageManager) {
13663
- const scripts = packageScripts(repoRoot);
13664
- const run = packageRunCommand(packageManager);
13665
- return unique([
13666
- ...auditCommandsFor(packageManager),
13667
- ...scriptCommand(scripts, "check", run),
13668
- ...scriptCommand(scripts, "test", run),
13669
- ...scriptCommand(scripts, "test:client", run),
13670
- ...scriptCommand(scripts, "test:e2e:smoke", run),
13671
- ...scriptCommand(scripts, "build", run)
13672
- ]).slice(0, 10);
13673
- }
13674
- function packageScripts(repoRoot) {
13675
- const parsed = readPackageJson2(repoRoot, "package.json");
13676
- const scripts = parsed?.scripts;
13677
- return scripts && typeof scripts === "object" ? Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string")) : {};
13678
- }
13679
- function scriptCommand(scripts, scriptName, runCommand) {
13680
- return scripts[scriptName] ? [`${runCommand} ${scriptName}`] : [];
13681
- }
13682
- function packageRunCommand(packageManager) {
13683
- switch (packageManager) {
13684
- case "npm":
13685
- return "npm run";
13686
- case "yarn":
13687
- return "yarn";
13688
- case "bun":
13689
- return "bun run";
13690
- default:
13691
- return "pnpm";
13692
- }
13693
- }
13694
- function auditCommandsFor(packageManager) {
13695
- switch (packageManager) {
13696
- case "pnpm":
13697
- return ["pnpm audit --prod"];
13698
- case "npm":
13699
- return ["npm audit --omit=dev"];
13700
- case "yarn":
13701
- return ["yarn npm audit --environment production"];
13702
- case "bun":
13703
- return ["bun audit"];
13704
- case "pip":
13705
- return ["pip-audit"];
13706
- case "bundler":
13707
- return ["bundle audit check"];
13708
- case "go":
13709
- return ["govulncheck ./..."];
13710
- case "cargo":
13711
- return ["cargo audit"];
13712
- default:
13713
- return [];
13714
- }
13715
- }
13716
- function dependencyGraphCommandsFor(packageManager, packageNames) {
13717
- const selected = packageNames.slice(0, 5);
13718
- if (selected.length === 0)
13719
- return [];
13720
- switch (packageManager) {
13721
- case "pnpm":
13722
- return selected.map((name) => `pnpm why ${name}`);
13723
- case "npm":
13724
- return selected.map((name) => `npm explain ${name}`);
13725
- case "yarn":
13726
- return selected.map((name) => `yarn why ${name}`);
13727
- case "bun":
13728
- return selected.map((name) => `bun pm why ${name}`);
13729
- case "go":
13730
- return selected.map((name) => `go mod why -m ${name}`);
13731
- case "cargo":
13732
- return selected.map((name) => `cargo tree -i ${name}`);
13733
- default:
13734
- return [];
13735
- }
13736
- }
13737
- function remediationCommandsFor(packageManager, packageNames) {
13738
- const selected = packageNames.slice(0, 5);
13739
- if (selected.length === 0)
13740
- return [];
13741
- switch (packageManager) {
13742
- case "pnpm":
13743
- return selected.map((name) => `pnpm up ${name} --latest --interactive=false`);
13744
- case "npm":
13745
- return selected.map((name) => `npm update ${name}`);
13746
- case "yarn":
13747
- return selected.map((name) => `yarn up ${name}`);
13748
- case "bun":
13749
- return selected.map((name) => `bun update ${name}`);
13750
- case "go":
13751
- return selected.map((name) => `go get ${name}@latest`);
13752
- case "cargo":
13753
- return selected.map((name) => `cargo update -p ${name}`);
13754
- default:
13755
- return [];
13756
- }
13757
- }
13758
- function residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles) {
13759
- return unique([
13760
- packageNames.length > 0 ? `Confirm vulnerable versions are gone for: ${packageNames.slice(0, 8).join(", ")}.` : "Name each advisory/package from the scanner before editing.",
13761
- changedDependencyFiles.some(isDependencyLockfile) ? "Inspect the lockfile diff for unexpected package churn outside the advisory path." : "Regenerate and inspect the lockfile if the fix changes dependency resolution.",
13762
- "Split major-version upgrades or broad framework upgrades into a separate mission unless the advisory requires them.",
13763
- "Record any remaining advisory ids, ignored dev-only findings, or transitive paths that still need owner judgment.",
13764
- ...auditCommandsFor(packageManager).map((command2) => `Rerun ${command2} after the lockfile update.`)
13765
- ]);
13766
- }
13767
- function confidenceFor(input) {
13768
- let score = 35;
13769
- if (input.changedDependencyFiles.length > 0)
13770
- score += 20;
13771
- if (input.packageSignals.length > 0)
13772
- score += 20;
13773
- if (input.lockfiles.length > 0)
13774
- score += 10;
13775
- if (input.validationCommands.length > 0)
13776
- score += 10;
13777
- return Math.min(95, score);
13778
- }
13779
- function summaryFor3(input) {
13780
- const packages = input.packageSignals.map((signal2) => signal2.name);
13781
- return [
13782
- `Detected ${input.packageManager} dependency remediation context.`,
13783
- `${input.manifests.length} manifest${input.manifests.length === 1 ? "" : "s"}, ${input.lockfiles.length} lockfile${input.lockfiles.length === 1 ? "" : "s"}.`,
13784
- input.changedDependencyFiles.length > 0 ? `Changed dependency files: ${input.changedDependencyFiles.slice(0, 6).join(", ")}.` : "No dependency files are changed yet.",
13785
- packages.length > 0 ? `Likely packages: ${packages.slice(0, 8).join(", ")}.` : "Package names still need advisory/scanner evidence.",
13786
- input.validationCommands.length > 0 ? `Validation starts with ${input.validationCommands[0]}.` : "Validation commands need repo-specific confirmation."
13787
- ].join(" ");
13788
- }
13789
- function cleanPackageName(input) {
13790
- return input.replace(/[),.;\]]+$/g, "").trim();
13791
- }
13792
- function looksLikePackageName(input) {
13793
- return /^@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?$/i.test(input);
13794
- }
13795
- function looksLikeVersionOrAdvisory(input) {
13796
- const normalized = input.toLowerCase();
13797
- return /^\d/.test(input) || /^cve-\d/i.test(input) || /^ghsa-/i.test(input) || [
13798
- "advisory",
13799
- "advisories",
13800
- "change",
13801
- "changes",
13802
- "dependency",
13803
- "dependencies",
13804
- "dependabot",
13805
- "finding",
13806
- "findings",
13807
- "package",
13808
- "packages",
13809
- "security",
13810
- "transitive",
13811
- "upgrade",
13812
- "upgrades",
13813
- "vulnerabilities",
13814
- "vulnerability",
13815
- "vulnerable"
13816
- ].includes(normalized);
13817
- }
13818
- var dependencyFilePatterns;
13819
- var init_dependencyRemediation = __esm({
13820
- "../core/dist/dependencyRemediation.js"() {
13821
- "use strict";
13822
- init_utils();
13823
- dependencyFilePatterns = [
13824
- "package.json",
13825
- "pnpm-lock.yaml",
13826
- "pnpm-workspace.yaml",
13827
- "package-lock.json",
13828
- "yarn.lock",
13829
- "bun.lock",
13830
- "npm-shrinkwrap.json",
13831
- "requirements.txt",
13832
- "poetry.lock",
13833
- "pyproject.toml",
13834
- "Pipfile.lock",
13835
- "Gemfile",
13836
- "Gemfile.lock",
13837
- "go.mod",
13838
- "go.sum",
13839
- "Cargo.toml",
13840
- "Cargo.lock"
13841
- ];
13842
- }
13843
- });
13844
-
13845
14024
  // ../core/dist/productOperatingContext.js
13846
14025
  function buildProductOperatingContext(scan, request) {
13847
14026
  const files = unique(request.files ?? []);
@@ -17164,7 +17343,7 @@ function missionGates(autonomy, recipe, options2) {
17164
17343
  label: "Library privacy gate",
17165
17344
  requiredTool: "brief.agent_session_insights",
17166
17345
  passCriteria: "Every candidate example is classified as derived-only, redacted transcript, or explicitly approved raw import before sharing.",
17167
- failureMode: "Do not publish raw prompts, secrets, customer data, private logs, or sensitive repo context without explicit approval."
17346
+ failureMode: "Do not share raw prompts, secrets, customer data, private logs, or sensitive repo context without explicit approval."
17168
17347
  }, {
17169
17348
  id: "reuse_quality",
17170
17349
  label: "Reuse quality gate",
@@ -17548,7 +17727,7 @@ function missionSteps(task, autonomy, recipe, options2) {
17548
17727
  id: "verify_receipts",
17549
17728
  title: "Verify ticket receipt ledger",
17550
17729
  objective: "Prove the ticket-writing receipt chain before another agent runs the ticket.",
17551
- agentAction: "Run Brief receipt verification and fix missing, mismatched, or broken-chain ticket proof before publishing the ticket.",
17730
+ agentAction: "Run Brief receipt verification and fix missing, mismatched, or broken-chain ticket proof before sending the ticket.",
17552
17731
  briefTool: "brief.verify_receipts",
17553
17732
  expectedOutput: "Receipt ledger verification with digest and chain-link status.",
17554
17733
  gateId: "ticket"
@@ -17777,7 +17956,7 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
17777
17956
  "executionContract.evidenceContract.mapProof",
17778
17957
  "context.likelyFiles"
17779
17958
  ],
17780
- stopIfMissing: isTicketWriter ? "Do not publish the ticket until product intent, owner judgment, acceptance criteria, and stop conditions are explicit." : "Do not edit until likely files, rules, tests, owners, and affected systems are grounded in Brief context."
17959
+ stopIfMissing: isTicketWriter ? "Do not send the ticket until product intent, owner judgment, acceptance criteria, and stop conditions are explicit." : "Do not edit until likely files, rules, tests, owners, and affected systems are grounded in Brief context."
17781
17960
  },
17782
17961
  ...isRuntimeFix ? [
17783
17962
  {
@@ -18173,7 +18352,7 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
18173
18352
  successCriteria: [
18174
18353
  "The context receipt cites the rules, source paths, tests, owners, and memories used by the agent.",
18175
18354
  recipe.id === "ticket_write" ? "The ticket separates human judgment from execution instructions and names unresolved decisions before automation starts." : recipe.id === "security_dependency" ? "The dependency vulnerability packet names advisory id, package, affected range, fixed range, manifest, lockfile, direct/transitive path, reachability, remediation choice, and rollback." : recipe.id === "security_finding" ? "The security finding packet names alert source, rule id, classification, source-to-sink or trust boundary, affected users/data, owner, exploitability, and validation path." : recipe.id === "security_testing" ? "The security test plan names the protected property, bad case, safe case, command, severity threshold, false-positive policy, owner, privacy boundary, and rollout mode." : recipe.id === "internal_app" ? "The app contract names the internal route, data source, auth context, allowed roles, row filters, audit events, and redaction rules before implementation." : recipe.id === "stale_pr" ? "The PR intent packet names original scope, unresolved review feedback, stale assumptions, validation, and handoff expectation." : recipe.id === "merge_conflict" ? "The conflict packet names conflicted files, both sides' intent, semantic conflicts, and validation before resolution." : recipe.id === "flaky_test" ? "The CI evidence packet names failing job or test, sample failure, platform, seed or timing signal, hypothesis, and validation path." : recipe.id === "small_refactor" ? "The mechanical scope packet names exact pattern, allowed files, exclusions, and expected behavior preservation." : recipe.id === "bug_investigation" ? "The hypothesis ledger names observed facts, prior attempts, plausible causes, disproven causes, next probes, and confidence." : recipe.id === "qa_edge_cases" ? "The edge-case inventory names current coverage, missing boundary cases, risky inputs, and validation commands." : recipe.id === "performance" ? "The benchmark packet names workload, command, metric, baseline, variance, success threshold, and rollback signal before edits." : recipe.id === "porting" ? "The source behavior packet names source paths, target paths, incompatible assumptions, owner decision, validation, and attribution notes." : recipe.id === "experiment" ? "The experiment contract names the hypothesis, success signal, throwaway boundary, rollback or discard path, and decision owner." : recipe.id === "session_review" ? "The session review uses receipt-derived activity and marks any need for raw prompts, logs, or transcripts explicitly." : recipe.id === "session_library" ? "The session library classifies every reusable example by proof quality, privacy posture, owner approval, and reuse trigger." : options2.needsRuntimeEvidence ? "The runtime evidence packet names the observed failure source, identifier, environment, time window, symptom, and open gaps." : options2.needsWorkItem ? "The work item packet names source, id, acceptance criteria, non-goals, and the update expected after execution." : recipe.id === "research" ? "The answer names sources, risks, and next steps without editing the repo." : "Required validation evidence is attached or the missing environment is explicitly explained.",
18176
- recipe.id === "ticket_write" ? "Acceptance criteria, constraints, validation plan, review bar, stop conditions, and update template are parseable from the ticket body." : recipe.id === "security_dependency" ? "Install integrity, build/typecheck, targeted tests, and scanner/audit evidence show the vulnerability is remediated without breaking the dependency path." : recipe.id === "security_finding" ? "The root cause is fixed with negative regression, safe abuse-case proof, or scanner rerun evidence; suppression-only fixes are explicitly rejected or owner-approved." : recipe.id === "security_testing" ? "The added test or scanner catches the intended bad case or has a clear preventive rationale, and rollout mode is calibrated for developer workflow." : recipe.id === "internal_app" ? "The built app follows repo-native UI/data patterns and validation proves the access-control contract." : recipe.id === "stale_pr" ? "The handoff names review feedback addressed, validation, remaining risk, and next reviewer action." : recipe.id === "merge_conflict" ? "The resolution preserves intended behavior from both sides or clearly escalates unresolved semantic conflicts." : recipe.id === "flaky_test" ? "The result stabilizes the test or explains the product root cause with evidence rather than hiding the signal." : recipe.id === "small_refactor" ? "The diff stays mechanical and validation proves no intended behavior changed." : recipe.id === "bug_investigation" ? "The root cause, fix, no-fix decision, or escalation is tied to evidence and includes a follow-up watch signal." : recipe.id === "qa_edge_cases" ? "The added or improved tests cover meaningful behavior risk and do not mute, skip, or obscure the signal." : recipe.id === "performance" ? "Before/after measurements, tradeoffs, and rollback signals are recorded before the optimization is accepted." : recipe.id === "porting" ? "The target behavior preserves source intent, attribution is handled, and intentional differences are explicit." : recipe.id === "experiment" ? "The keep, discard, or formalize decision is recorded with validation and remaining production-readiness gaps." : recipe.id === "session_review" ? "Run cards or skill drafts are tied to actual receipts, validation quality, review outcomes, and privacy posture." : recipe.id === "session_library" ? "The published examples are derived from approved receipts or explicitly approved imports and include first-run commands, stop conditions, and validation expectations." : options2.needsRuntimeEvidence ? "The root cause, fix, validation, and incident or ticket update are tied back to the runtime evidence." : "The mission output is tied back to the selected recipe and context receipt.",
18355
+ recipe.id === "ticket_write" ? "Acceptance criteria, constraints, validation plan, review bar, stop conditions, and update template are parseable from the ticket body." : recipe.id === "security_dependency" ? "Install integrity, build/typecheck, targeted tests, and scanner/audit evidence show the vulnerability is remediated without breaking the dependency path." : recipe.id === "security_finding" ? "The root cause is fixed with negative regression, safe abuse-case proof, or scanner rerun evidence; suppression-only fixes are explicitly rejected or owner-approved." : recipe.id === "security_testing" ? "The added test or scanner catches the intended bad case or has a clear preventive rationale, and rollout mode is calibrated for developer workflow." : recipe.id === "internal_app" ? "The built app follows repo-native UI/data patterns and validation proves the access-control contract." : recipe.id === "stale_pr" ? "The handoff names review feedback addressed, validation, remaining risk, and next reviewer action." : recipe.id === "merge_conflict" ? "The resolution preserves intended behavior from both sides or clearly escalates unresolved semantic conflicts." : recipe.id === "flaky_test" ? "The result stabilizes the test or explains the product root cause with evidence rather than hiding the signal." : recipe.id === "small_refactor" ? "The diff stays mechanical and validation proves no intended behavior changed." : recipe.id === "bug_investigation" ? "The root cause, fix, no-fix decision, or escalation is tied to evidence and includes a follow-up watch signal." : recipe.id === "qa_edge_cases" ? "The added or improved tests cover meaningful behavior risk and do not mute, skip, or obscure the signal." : recipe.id === "performance" ? "Before/after measurements, tradeoffs, and rollback signals are recorded before the optimization is accepted." : recipe.id === "porting" ? "The target behavior preserves source intent, attribution is handled, and intentional differences are explicit." : recipe.id === "experiment" ? "The keep, discard, or formalize decision is recorded with validation and remaining production-readiness gaps." : recipe.id === "session_review" ? "Run cards or skill drafts are tied to actual receipts, validation quality, review outcomes, and privacy posture." : recipe.id === "session_library" ? "The shared examples are derived from approved receipts or explicitly approved imports and include first-run commands, stop conditions, and validation expectations." : options2.needsRuntimeEvidence ? "The root cause, fix, validation, and incident or ticket update are tied back to the runtime evidence." : "The mission output is tied back to the selected recipe and context receipt.",
18177
18356
  autonomy === "read_only" ? "No repo files are changed." : "The local diff has no blocker or high Brief findings before human review.",
18178
18357
  "Useful repeated feedback is saved or intentionally skipped with a reason."
18179
18358
  ],
@@ -19012,10 +19191,10 @@ function missionHookEvents(recipe, autonomy, options2) {
19012
19191
  if (recipe.id === "session_library") {
19013
19192
  events.push({
19014
19193
  id: "before_session_library_publish",
19015
- label: "Before library publish",
19194
+ label: "Before team sharing",
19016
19195
  timing: "before",
19017
19196
  trigger: "An agent turns previous sessions, transcripts, prompts, or run cards into shared examples.",
19018
- action: "Require receipt proof, privacy classification, owner approval, and explicit raw-import approval before publishing examples to the team.",
19197
+ action: "Require receipt proof, privacy classification, owner approval, and explicit raw-import approval before sharing examples with the team.",
19019
19198
  mode: "blocking"
19020
19199
  });
19021
19200
  }
@@ -20251,7 +20430,7 @@ var init_missionPlan = __esm({
20251
20430
  guidance: [
20252
20431
  "Keep one curator responsible for privacy classification and final library shape.",
20253
20432
  "Use parallel agents only to summarize candidate receipts or draft examples from approved sources.",
20254
- "Do not publish raw transcripts, secrets, logs, or customer data without explicit approval."
20433
+ "Do not share raw transcripts, secrets, logs, or customer data without explicit approval."
20255
20434
  ]
20256
20435
  }
20257
20436
  }),
@@ -20758,7 +20937,7 @@ function privacyBoundaryForRecipe(recipeId) {
20758
20937
  if (recipeId === "security_finding") {
20759
20938
  return [
20760
20939
  "redact raw secrets, tokens, cookies, customer payloads, and private logs before agent use",
20761
- "do not publish exploit strings or abuse-case details outside the approved repo/security channel",
20940
+ "do not share exploit strings or abuse-case details outside the approved repo/security channel",
20762
20941
  "provider rotation or invalidation status is tracked without exposing the replacement credential",
20763
20942
  "false-positive and accepted-risk decisions need named owner approval and audit trail"
20764
20943
  ];
@@ -20789,7 +20968,7 @@ function privacyBoundaryForRecipe(recipeId) {
20789
20968
  return [
20790
20969
  "derive examples from receipts before raw transcripts",
20791
20970
  "require approval for prompts, logs, secrets, or customer data",
20792
- "publish concise run cards, not private session dumps"
20971
+ "share concise run cards, not private session dumps"
20793
20972
  ];
20794
20973
  }
20795
20974
  return [
@@ -26220,7 +26399,7 @@ var init_agentSetup = __esm({
26220
26399
  "ide",
26221
26400
  "generic"
26222
26401
  ];
26223
- briefPublishedPackageSpec = "runbrief@0.1.2";
26402
+ briefPublishedPackageSpec = "runbrief@0.1.3";
26224
26403
  TARGET_LABELS = {
26225
26404
  terminal: "Terminal agent",
26226
26405
  vscode: "VS Code",
@@ -27281,7 +27460,7 @@ function summarizeBeforeYouCodeSharedContext(delivery) {
27281
27460
  next: "Shared guidance is not available in this session; keep new learning personal until the workspace connection is ready."
27282
27461
  };
27283
27462
  }
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.";
27463
+ 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 make it available to the team only after the pattern is proven.";
27285
27464
  return {
27286
27465
  status: delivery.status,
27287
27466
  workspaceId: delivery.workspaceId,
@@ -27424,11 +27603,13 @@ function buildBeforeYouCodeMap(scan, input) {
27424
27603
  files,
27425
27604
  maxLines: 18
27426
27605
  });
27606
+ const mapCoverage = mapCoverageFor2(scan);
27427
27607
  const stopSignals2 = unique([
27428
27608
  "Wrong repo, branch, or worktree.",
27429
27609
  ...target.branch?.startsWith("detached@") ? ["Detached HEAD; select the intended feature branch before editing."] : [],
27430
27610
  ...insights.agentPacket.stopSignals,
27431
27611
  ...productOperatingContext.stopSignals.slice(0, 3),
27612
+ ...mapCoverage.status === "partial" ? [mapCoverage.summary] : [],
27432
27613
  ...target.dirty && files.length === 0 ? ["Dirty worktree is broad; classify baseline before editing."] : []
27433
27614
  ]).slice(0, 5);
27434
27615
  const status = inspectFirst.length === 0 && reuseCandidates.length === 0 ? "needs_narrower_scope" : "ready";
@@ -27448,6 +27629,7 @@ function buildBeforeYouCodeMap(scan, input) {
27448
27629
  reuseCandidates,
27449
27630
  likelyTests,
27450
27631
  productOperatingContext,
27632
+ mapCoverage,
27451
27633
  ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
27452
27634
  referenceFirst,
27453
27635
  validationChecklist,
@@ -27460,6 +27642,7 @@ function buildBeforeYouCodeMap(scan, input) {
27460
27642
  reuseCandidates,
27461
27643
  likelyTests,
27462
27644
  productOperatingContext,
27645
+ mapCoverage,
27463
27646
  validationChecklist,
27464
27647
  stopSignals: stopSignals2,
27465
27648
  ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
@@ -27479,7 +27662,9 @@ function buildBeforeYouCodeMap(scan, input) {
27479
27662
  candidates: reuseCandidates,
27480
27663
  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."
27481
27664
  },
27665
+ mapCoverage,
27482
27666
  ...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
27667
+ rules: context.rules.slice(0, 8),
27483
27668
  referenceFirst,
27484
27669
  likelyTests,
27485
27670
  exactTests,
@@ -27491,6 +27676,19 @@ function buildBeforeYouCodeMap(scan, input) {
27491
27676
  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."
27492
27677
  };
27493
27678
  }
27679
+ function mapCoverageFor2(scan) {
27680
+ const mappedNodes = scan.stats.mapNodeCount;
27681
+ const omittedNodes = Math.max(0, scan.stats.mapOmittedCount ?? 0);
27682
+ const candidateNodes = Math.max(mappedNodes, scan.stats.mapTotalCandidateCount ?? mappedNodes + omittedNodes);
27683
+ const partial = Boolean(scan.stats.mapPartial) || omittedNodes > 0;
27684
+ return {
27685
+ status: partial ? "partial" : "complete",
27686
+ mappedNodes,
27687
+ candidateNodes,
27688
+ omittedNodes,
27689
+ summary: partial ? `Code map is partial: ${mappedNodes} of ${candidateNodes} candidate nodes are available; narrow by feature, folder, or symbol before treating a missing reuse candidate as proof none exists.` : `Code map covers all ${mappedNodes} indexed candidate nodes for this repo.`
27690
+ };
27691
+ }
27494
27692
  function reuseStatusFor(candidates) {
27495
27693
  if (candidates.length === 0)
27496
27694
  return "missing";
@@ -27611,6 +27809,7 @@ function cockpitFor(input) {
27611
27809
  "Broad e2e or full-suite tests unless routing, auth, persistence, or cross-page state changed."
27612
27810
  ];
27613
27811
  const stopSignals2 = unique([
27812
+ ...input.mapCoverage.status === "partial" ? [input.mapCoverage.summary] : [],
27614
27813
  ...input.stopSignals.slice(0, 3),
27615
27814
  ...mode === "lightweight" ? [
27616
27815
  "The tiny pass starts touching behavior, state, data, auth, or multiple surfaces."
@@ -27798,55 +27997,55 @@ function diversifyJourneyStages(ranked, task) {
27798
27997
  ];
27799
27998
  }
27800
27999
  function journeyStagePathPreference(stage, filePath) {
27801
- const path20 = filePath.toLowerCase();
28000
+ const path21 = filePath.toLowerCase();
27802
28001
  if (stage === "acquisition") {
27803
- if (/landinginteractive\.[cm]?[tj]sx?$/.test(path20))
28002
+ if (/landinginteractive\.[cm]?[tj]sx?$/.test(path21))
27804
28003
  return 40;
27805
- if (/apps\/web\/src\/app\/page\.[cm]?[tj]sx?$/.test(path20))
28004
+ if (/apps\/web\/src\/app\/page\.[cm]?[tj]sx?$/.test(path21))
27806
28005
  return 35;
27807
28006
  }
27808
28007
  if (stage === "access") {
27809
- if (/apps\/web\/src\/app\/login\/page\.[cm]?[tj]sx?$/.test(path20))
28008
+ if (/apps\/web\/src\/app\/login\/page\.[cm]?[tj]sx?$/.test(path21))
27810
28009
  return 40;
27811
- if (/api\/auth\/magic-link\/route\.[cm]?[tj]s$/.test(path20))
28010
+ if (/api\/auth\/magic-link\/route\.[cm]?[tj]s$/.test(path21))
27812
28011
  return 35;
27813
28012
  }
27814
28013
  if (stage === "workspace") {
27815
- if (/api\/v1\/workspaces\/bootstrap\/route\.[cm]?[tj]s$/.test(path20)) {
28014
+ if (/api\/v1\/workspaces\/bootstrap\/route\.[cm]?[tj]s$/.test(path21)) {
27816
28015
  return 40;
27817
28016
  }
27818
28017
  }
27819
28018
  if (stage === "activation") {
27820
- if (/api\/v1\/workspaces\/[^/]+\/api-keys\/route\.[cm]?[tj]s$/.test(path20)) {
28019
+ if (/api\/v1\/workspaces\/[^/]+\/api-keys\/route\.[cm]?[tj]s$/.test(path21)) {
27821
28020
  return 40;
27822
28021
  }
27823
- if (/workspace-agent-actions\.[cm]?[tj]s$/.test(path20))
28022
+ if (/workspace-agent-actions\.[cm]?[tj]s$/.test(path21))
27824
28023
  return 35;
27825
- if (/agent-handoff-composer\.[cm]?[tj]s$/.test(path20))
28024
+ if (/agent-handoff-composer\.[cm]?[tj]s$/.test(path21))
27826
28025
  return 30;
27827
28026
  }
27828
28027
  if (stage === "collaboration") {
27829
- if (/api\/v1\/workspaces\/[^/]+\/invitations\/route\.[cm]?[tj]s$/.test(path20)) {
28028
+ if (/api\/v1\/workspaces\/[^/]+\/invitations\/route\.[cm]?[tj]s$/.test(path21)) {
27830
28029
  return 40;
27831
28030
  }
27832
- if (/api\/v1\/workspaces\/[^/]+\/members\/[^/]+\/route/.test(path20)) {
28031
+ if (/api\/v1\/workspaces\/[^/]+\/members\/[^/]+\/route/.test(path21)) {
27833
28032
  return 30;
27834
28033
  }
27835
28034
  }
27836
28035
  if (stage === "monetization") {
27837
- if (/billing\/checkout\/route\.[cm]?[tj]s$/.test(path20))
28036
+ if (/billing\/checkout\/route\.[cm]?[tj]s$/.test(path21))
27838
28037
  return 40;
27839
- if (/pricing\/page\.[cm]?[tj]sx?$/.test(path20))
28038
+ if (/pricing\/page\.[cm]?[tj]sx?$/.test(path21))
27840
28039
  return 35;
27841
28040
  }
27842
28041
  return 0;
27843
28042
  }
27844
- function beforeYouCodePathIntentScore(path20, task) {
27845
- const lowerPath = (path20.split("#", 1)[0] ?? path20).toLowerCase();
28043
+ function beforeYouCodePathIntentScore(path21, task) {
28044
+ const lowerPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
27846
28045
  const lowerTask = task.toLowerCase();
27847
- let score = namedCodePathIntentScore(path20, task);
28046
+ let score = namedCodePathIntentScore(path21, task);
27848
28047
  const wantsInterface = /\b(app|design|header|interface|landing|layout|mobile|navigation|page|responsive|spacing|ui|ux|visual|workspace)\b/.test(lowerTask);
27849
- score += lexicalPathTaskScore(path20, task);
28048
+ score += lexicalPathTaskScore(path21, task);
27850
28049
  const wantsLanding = /\b(hero|homepage|landing)\b/.test(lowerTask);
27851
28050
  const wantsAppShell = /\b(app|header|internal|mobile|navigation|responsive|sidebar|workspace)\b/.test(lowerTask);
27852
28051
  const wantsAuth = /\b(auth|authentication|callback|login|magic link|otp|session|sign[ -]?in|sign[ -]?out)\b/.test(lowerTask);
@@ -27996,8 +28195,8 @@ function beforeYouCodePathIntentScore(path20, task) {
27996
28195
  }
27997
28196
  return score;
27998
28197
  }
27999
- function beforeYouCodeIntentSummary(path20, task) {
28000
- const normalizedPath = (path20.split("#", 1)[0] ?? path20).toLowerCase();
28198
+ function beforeYouCodeIntentSummary(path21, task) {
28199
+ const normalizedPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
28001
28200
  if (!isInternalAppTask(task))
28002
28201
  return void 0;
28003
28202
  if (/apps\/web\/src\/app\/app\/page\.[cm]?[tj]sx?$/.test(normalizedPath)) {
@@ -28023,7 +28222,7 @@ function prefersPrimaryWorkspaceTask(task) {
28023
28222
  return true;
28024
28223
  return /\b(design|designer|figma|screenshot|visual|ui|ux|layout|spacing|typography|responsive|mobile)\b/i.test(task);
28025
28224
  }
28026
- function lexicalPathTaskScore(path20, task) {
28225
+ function lexicalPathTaskScore(path21, task) {
28027
28226
  const ignored = /* @__PURE__ */ new Set([
28028
28227
  "agent",
28029
28228
  "brief",
@@ -28043,7 +28242,7 @@ function lexicalPathTaskScore(path20, task) {
28043
28242
  taskWords.add("workspace");
28044
28243
  taskWords.add("pane");
28045
28244
  }
28046
- const pathWords = path20.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 4 && !ignored.has(word));
28245
+ const pathWords = path21.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 4 && !ignored.has(word));
28047
28246
  const matches = unique(pathWords.filter((word) => taskWords.has(word)));
28048
28247
  const structuralBonus = matches.reduce((score, word) => {
28049
28248
  if (!["header", "pane", "shell", "sidebar", "workspace"].includes(word)) {
@@ -28117,6 +28316,7 @@ function compactTextFor2(input) {
28117
28316
  `Before coding: ${input.headline}`,
28118
28317
  `Target: ${input.target.repoName} | ${input.target.branch ?? "branch unknown"} | ${input.target.repoRoot}`,
28119
28318
  `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(", ")}` : ""}`,
28319
+ `Map coverage: ${input.mapCoverage.summary}`,
28120
28320
  ...input.target.requestedFiles.length ? [`Scope files: ${input.target.requestedFiles.slice(0, 5).join(", ")}`] : [],
28121
28321
  ...input.teamGuidance ? [
28122
28322
  `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"}`
@@ -28167,16 +28367,20 @@ var init_agentBeforeYouCode = __esm({
28167
28367
  // ../core/dist/agentWorkPacket.js
28168
28368
  function buildAgentWorkPacket(input) {
28169
28369
  const before = input.beforeYouCode;
28170
- const rules = [
28370
+ const rules = uniqueRules2([
28171
28371
  ...(before.teamGuidance?.applied ?? []).map((item) => ({
28172
28372
  rule: item.label,
28173
28373
  source: `team:${item.kind}`
28174
28374
  })),
28375
+ ...(before.rules ?? []).map((item) => ({
28376
+ rule: compact(item.body || item.title, 180),
28377
+ source: item.sourcePaths[0] ?? "repo guidance"
28378
+ })),
28175
28379
  ...before.productOperatingContext.productRules.map((item) => ({
28176
28380
  rule: item.rule,
28177
28381
  source: item.source
28178
28382
  }))
28179
- ].slice(0, 3);
28383
+ ]).slice(0, 3);
28180
28384
  const tests = before.exactTests.length > 0 ? before.exactTests.slice(0, 3).map((test) => ({
28181
28385
  command: compact(test.command, 180),
28182
28386
  reason: compact(test.reason, 180),
@@ -28192,7 +28396,8 @@ function buildAgentWorkPacket(input) {
28192
28396
  ]).slice(0, 3);
28193
28397
  const stopConditions = unique2([
28194
28398
  ...before.stopSignals,
28195
- ...before.productOperatingContext.stopSignals
28399
+ ...before.productOperatingContext.stopSignals,
28400
+ ...before.mapCoverage.status === "partial" ? [before.mapCoverage.summary] : []
28196
28401
  ]).slice(0, 3);
28197
28402
  const nextAction = input.nextAction ?? before.next ?? "Inspect the listed files and make the reuse decision before editing.";
28198
28403
  const packet = {
@@ -28220,6 +28425,7 @@ function buildAgentWorkPacket(input) {
28220
28425
  why: compact(item.why, 140)
28221
28426
  }))
28222
28427
  },
28428
+ mapCoverage: before.mapCoverage,
28223
28429
  rules,
28224
28430
  tests,
28225
28431
  acceptance,
@@ -28236,6 +28442,7 @@ function formatAgentWorkPacket(packet) {
28236
28442
  `Interpretation: ${packet.interpretation}`,
28237
28443
  `Inspect first: ${inlineList(packet.inspectFirst.map((item) => `${item.path} - ${item.why}`))}`,
28238
28444
  `Reuse (${packet.reuse.status}): ${packet.reuse.decision}; ${inlineList(packet.reuse.candidates.map((item) => `${item.path} - ${item.action}`))}`,
28445
+ `Map: ${packet.mapCoverage.summary}`,
28239
28446
  `Rules: ${inlineList(packet.rules.map((item) => `${item.rule} [${item.source}]`))}`,
28240
28447
  `Focused tests: ${inlineList(packet.tests.map((item) => `${item.command} - ${item.reason}`))}`,
28241
28448
  `Acceptance: ${inlineList(packet.acceptance)}`,
@@ -28254,6 +28461,16 @@ function compact(value, maxLength) {
28254
28461
  function unique2(values) {
28255
28462
  return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
28256
28463
  }
28464
+ function uniqueRules2(rules) {
28465
+ const seen = /* @__PURE__ */ new Set();
28466
+ return rules.filter((item) => {
28467
+ const key = item.rule.toLowerCase();
28468
+ if (!item.rule || seen.has(key))
28469
+ return false;
28470
+ seen.add(key);
28471
+ return true;
28472
+ });
28473
+ }
28257
28474
  var init_agentWorkPacket = __esm({
28258
28475
  "../core/dist/agentWorkPacket.js"() {
28259
28476
  "use strict";
@@ -31192,6 +31409,7 @@ var init_diffReview = __esm({
31192
31409
  "handler",
31193
31410
  "helpers",
31194
31411
  "delete",
31412
+ "dynamic",
31195
31413
  "get",
31196
31414
  "head",
31197
31415
  "input",
@@ -33102,7 +33320,7 @@ function cloudSyncStatus(env = process.env) {
33102
33320
  return {
33103
33321
  configured: false,
33104
33322
  privacyMode,
33105
- reason: privacyMode === "local-only" ? "Cloud sync is disabled by privacy mode." : "Set BRIEF_API_URL and BRIEF_API_KEY to enable MCP cloud sync."
33323
+ reason: privacyMode === "local-only" ? "Cloud sync is disabled by privacy mode." : "Connect Brief from the app to authorize this device, or provide BRIEF_API_URL and BRIEF_API_KEY for an approved legacy setup."
33106
33324
  };
33107
33325
  }
33108
33326
  const status = {
@@ -33259,6 +33477,64 @@ async function fetchTeamContext(repoRoot, env = process.env, fetcher = fetch) {
33259
33477
  });
33260
33478
  return getTeamContextCloud(config, payload, fetcher);
33261
33479
  }
33480
+ async function claimRemoteLoopRun(runnerId, leaseSeconds = 180, env = process.env, fetcher = fetch) {
33481
+ const config = cloudConfigFromEnv(env);
33482
+ if (!config) throw new Error("Brief cloud connection is not configured for the runner.");
33483
+ const payload = claimLoopRunRequestSchema.parse({ runnerId, leaseSeconds });
33484
+ const body = await loopRunRequest(
33485
+ config,
33486
+ "/api/v1/mcp/loop-runs/claim",
33487
+ "POST",
33488
+ payload,
33489
+ fetcher
33490
+ );
33491
+ if (!body.ok) throw new Error(body.message ?? "Brief could not claim a loop run.");
33492
+ if (!body.run) return null;
33493
+ const parsed = loopRunSummarySchema.safeParse(body.run);
33494
+ if (!parsed.success) throw new Error("Brief returned an invalid loop run.");
33495
+ return parsed.data;
33496
+ }
33497
+ async function updateRemoteLoopRun(runId, input, env = process.env, fetcher = fetch) {
33498
+ const config = cloudConfigFromEnv(env);
33499
+ if (!config) throw new Error("Brief cloud connection is not configured for the runner.");
33500
+ const payload = updateLoopRunRequestSchema.parse(input);
33501
+ const body = await loopRunRequest(
33502
+ config,
33503
+ `/api/v1/mcp/loop-runs/${encodeURIComponent(runId)}`,
33504
+ "PATCH",
33505
+ payload,
33506
+ fetcher
33507
+ );
33508
+ if (!body.ok) throw new Error(body.message ?? "Brief could not update the loop run.");
33509
+ const parsed = loopRunSummarySchema.safeParse(body.run);
33510
+ if (!parsed.success) throw new Error("Brief returned an invalid updated loop run.");
33511
+ return parsed.data;
33512
+ }
33513
+ async function loopRunRequest(config, route, method, payload, fetcher) {
33514
+ const controller = new AbortController();
33515
+ const timeout = setTimeout(() => controller.abort(), syncTimeoutMs);
33516
+ try {
33517
+ const response = await fetcher(`${config.apiUrl}${route}`, {
33518
+ method,
33519
+ headers: {
33520
+ authorization: `Bearer ${config.apiKey}`,
33521
+ "content-type": "application/json"
33522
+ },
33523
+ body: JSON.stringify(payload),
33524
+ signal: controller.signal
33525
+ });
33526
+ const body = await safeJson(response);
33527
+ const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
33528
+ const result = {
33529
+ ok: response.ok && record.ok === true
33530
+ };
33531
+ if (typeof record.message === "string") result.message = record.message;
33532
+ if (record.run !== void 0) result.run = record.run;
33533
+ return result;
33534
+ } finally {
33535
+ clearTimeout(timeout);
33536
+ }
33537
+ }
33262
33538
  function toRepoScanSummary(scan, privacyMode) {
33263
33539
  return {
33264
33540
  repoName: scan.repoName,
@@ -33304,7 +33580,7 @@ function skippedResult(env) {
33304
33580
  return {
33305
33581
  status: "skipped",
33306
33582
  privacyMode,
33307
- reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because BRIEF_API_URL or BRIEF_API_KEY is missing."
33583
+ reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because this device is not connected. Create a browser-authorized connection from the Brief app, then restart the agent."
33308
33584
  };
33309
33585
  }
33310
33586
  function skippedTeamContextResult(env) {
@@ -33312,7 +33588,7 @@ function skippedTeamContextResult(env) {
33312
33588
  return {
33313
33589
  status: "skipped",
33314
33590
  privacyMode,
33315
- reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because BRIEF_API_URL or BRIEF_API_KEY is missing."
33591
+ reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because this device is not connected. Create a browser-authorized connection from the Brief app, then restart the agent."
33316
33592
  };
33317
33593
  }
33318
33594
  function normalizeApiUrl(apiUrl) {
@@ -33736,9 +34012,9 @@ function issuesValue(value) {
33736
34012
  if (typeof item === "string") return item;
33737
34013
  if (!item || typeof item !== "object") return "";
33738
34014
  const record = item;
33739
- const path20 = Array.isArray(record.path) ? record.path.join(".") : "";
34015
+ const path21 = Array.isArray(record.path) ? record.path.join(".") : "";
33740
34016
  const message = stringValue(record.message);
33741
- return [path20, message].filter(Boolean).join(": ");
34017
+ return [path21, message].filter(Boolean).join(": ");
33742
34018
  }).filter(Boolean).slice(0, 5).join("; ");
33743
34019
  }
33744
34020
  var syncTimeoutMs, canonicalBriefApiUrl, legacyBriefApiUrls;
@@ -34129,14 +34405,178 @@ var init_missionInputOptions = __esm({
34129
34405
  }
34130
34406
  });
34131
34407
 
34132
- // ../mcp/src/workspace.ts
34133
- import { existsSync as existsSync5, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
34408
+ // ../mcp/src/cloudCredentials.ts
34409
+ import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
34410
+ import os from "node:os";
34134
34411
  import path14 from "node:path";
34412
+ async function bootstrapCloudCredentials(env = process.env) {
34413
+ if (env.BRIEF_PRIVACY_MODE === "local-only") {
34414
+ return { status: "skipped", reason: "local_only" };
34415
+ }
34416
+ if (env.BRIEF_API_URL && env.BRIEF_API_KEY) {
34417
+ return { status: "configured", source: "env" };
34418
+ }
34419
+ const credentialPath = credentialsPath(env);
34420
+ const requestedOrigin = originFrom(
34421
+ env.BRIEF_API_URL ?? env.BRIEF_CONNECT_URL
34422
+ );
34423
+ const requestedWorkspace = env.BRIEF_WORKSPACE_ID?.trim();
34424
+ const stored = readCredentialFile(credentialPath);
34425
+ const existing = stored.credentials.find((credential2) => {
34426
+ if (requestedOrigin && originFrom(credential2.apiUrl) !== requestedOrigin) {
34427
+ return false;
34428
+ }
34429
+ return !requestedWorkspace || credential2.workspaceId === requestedWorkspace;
34430
+ });
34431
+ if (existing) {
34432
+ applyCredential(env, existing);
34433
+ return { status: "configured", source: "local_store" };
34434
+ }
34435
+ const connectUrl = env.BRIEF_CONNECT_URL?.trim();
34436
+ const connectCode = env.BRIEF_CONNECT_CODE?.trim();
34437
+ if (!connectUrl || !connectCode) {
34438
+ return {
34439
+ status: "skipped",
34440
+ reason: "no_cloud_credential_or_pairing_setup"
34441
+ };
34442
+ }
34443
+ let response;
34444
+ try {
34445
+ response = await fetch(connectUrl, {
34446
+ method: "POST",
34447
+ headers: {
34448
+ accept: "application/json",
34449
+ "content-type": "application/json"
34450
+ },
34451
+ body: JSON.stringify({ code: connectCode }),
34452
+ signal: AbortSignal.timeout(1e4)
34453
+ });
34454
+ } catch {
34455
+ return {
34456
+ status: "failed",
34457
+ reason: "Could not reach the Brief connection service. Check your network and try a fresh setup from the Brief app."
34458
+ };
34459
+ }
34460
+ const body = await response.json().catch(() => null);
34461
+ if (!response.ok || !body?.ok || !body.apiUrl || !body.apiKey || !apiKeyPattern.test(body.apiKey) || !body.workspaceId || !body.privacyMode) {
34462
+ return {
34463
+ status: "failed",
34464
+ reason: body?.message ?? "This Brief setup link is expired or already used. Create a fresh connection from the Brief app."
34465
+ };
34466
+ }
34467
+ const credential = {
34468
+ apiUrl: body.apiUrl,
34469
+ apiKey: body.apiKey,
34470
+ workspaceId: body.workspaceId,
34471
+ privacyMode: body.privacyMode,
34472
+ ...body.connectorId ? { connectorId: body.connectorId } : {},
34473
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
34474
+ };
34475
+ try {
34476
+ writeCredentialFile(credentialPath, [
34477
+ ...stored.credentials.filter(
34478
+ (item) => item.workspaceId !== credential.workspaceId || originFrom(item.apiUrl) !== originFrom(credential.apiUrl)
34479
+ ),
34480
+ credential
34481
+ ]);
34482
+ } catch {
34483
+ return {
34484
+ status: "failed",
34485
+ reason: "Brief connected, but could not save the local credential. Check permissions for the local Brief config directory and try again."
34486
+ };
34487
+ }
34488
+ applyCredential(env, credential);
34489
+ return { status: "configured", source: "pairing" };
34490
+ }
34491
+ function forgetCloudCredentials(env = process.env) {
34492
+ const credentialPath = credentialsPath(env);
34493
+ const stored = readCredentialFile(credentialPath);
34494
+ const requestedOrigin = originFrom(
34495
+ env.BRIEF_API_URL ?? env.BRIEF_CONNECT_URL
34496
+ );
34497
+ const requestedWorkspace = env.BRIEF_WORKSPACE_ID?.trim();
34498
+ const remaining = stored.credentials.filter((credential) => {
34499
+ if (requestedOrigin && originFrom(credential.apiUrl) !== requestedOrigin) {
34500
+ return true;
34501
+ }
34502
+ return Boolean(
34503
+ requestedWorkspace && credential.workspaceId !== requestedWorkspace
34504
+ );
34505
+ });
34506
+ writeCredentialFile(credentialPath, remaining);
34507
+ }
34508
+ function defaultCredentialsPath(env = process.env) {
34509
+ return credentialsPath(env);
34510
+ }
34511
+ function credentialsPath(env) {
34512
+ return env.BRIEF_CREDENTIALS_FILE?.trim() || path14.join(
34513
+ env.XDG_CONFIG_HOME?.trim() || path14.join(os.homedir(), ".config"),
34514
+ "runbrief",
34515
+ "credentials.json"
34516
+ );
34517
+ }
34518
+ function readCredentialFile(filePath) {
34519
+ try {
34520
+ const parsed = JSON.parse(readFileSync3(filePath, "utf8"));
34521
+ if (parsed.version !== 1 || !Array.isArray(parsed.credentials)) {
34522
+ return { version: 1, credentials: [] };
34523
+ }
34524
+ return {
34525
+ version: 1,
34526
+ credentials: parsed.credentials.filter(isStoredCredential)
34527
+ };
34528
+ } catch {
34529
+ return { version: 1, credentials: [] };
34530
+ }
34531
+ }
34532
+ function writeCredentialFile(filePath, credentials) {
34533
+ mkdirSync2(path14.dirname(filePath), { recursive: true, mode: 448 });
34534
+ chmodSync(path14.dirname(filePath), 448);
34535
+ writeFileSync5(
34536
+ filePath,
34537
+ `${JSON.stringify({ version: 1, credentials }, null, 2)}
34538
+ `,
34539
+ { encoding: "utf8", mode: 384 }
34540
+ );
34541
+ chmodSync(filePath, 384);
34542
+ }
34543
+ function applyCredential(env, credential) {
34544
+ env.BRIEF_API_URL = credential.apiUrl;
34545
+ env.BRIEF_API_KEY = credential.apiKey;
34546
+ env.BRIEF_WORKSPACE_ID = credential.workspaceId;
34547
+ env.BRIEF_PRIVACY_MODE = credential.privacyMode;
34548
+ }
34549
+ function originFrom(value) {
34550
+ if (!value) return void 0;
34551
+ try {
34552
+ return new URL(value).origin;
34553
+ } catch {
34554
+ return void 0;
34555
+ }
34556
+ }
34557
+ function isStoredCredential(value) {
34558
+ if (!value || typeof value !== "object") return false;
34559
+ const candidate = value;
34560
+ return Boolean(
34561
+ typeof candidate.apiUrl === "string" && typeof candidate.apiKey === "string" && apiKeyPattern.test(candidate.apiKey) && typeof candidate.workspaceId === "string" && typeof candidate.privacyMode === "string" && typeof candidate.updatedAt === "string"
34562
+ );
34563
+ }
34564
+ var apiKeyPattern;
34565
+ var init_cloudCredentials = __esm({
34566
+ "../mcp/src/cloudCredentials.ts"() {
34567
+ "use strict";
34568
+ apiKeyPattern = /^brf_(live|test)_[a-zA-Z0-9]{8,}_[a-zA-Z0-9]{24,}$/;
34569
+ }
34570
+ });
34571
+
34572
+ // ../mcp/src/workspace.ts
34573
+ import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
34574
+ import path15 from "node:path";
34135
34575
  function resolveRepoRoot(repoPath, options2 = {}) {
34136
34576
  return resolveRepoRootDetails(repoPath, options2).repoRoot;
34137
34577
  }
34138
34578
  function resolveRepoRootDetails(repoPath, options2 = {}) {
34139
- const cwd = path14.resolve(options2.cwd ?? process.cwd());
34579
+ const cwd = path15.resolve(options2.cwd ?? process.cwd());
34140
34580
  const env = options2.env ?? process.env;
34141
34581
  const cwdResolution = resolveExistingRoot(cwd, "cwd");
34142
34582
  const cwdIsCustomerWorktree = isGitWorktree(cwdResolution.repoRoot) && !looksLikeBriefSourceCheckout(cwdResolution.repoRoot);
@@ -34150,7 +34590,7 @@ function resolveRepoRootDetails(repoPath, options2 = {}) {
34150
34590
  ]);
34151
34591
  const explicitRepoPath = repoPath?.trim();
34152
34592
  if (explicitRepoPath) {
34153
- const explicitPathKind = path14.isAbsolute(explicitRepoPath) ? "absolute" : "relative";
34593
+ const explicitPathKind = path15.isAbsolute(explicitRepoPath) ? "absolute" : "relative";
34154
34594
  if (explicitPathKind === "relative" && envCandidates.length > 1) {
34155
34595
  return {
34156
34596
  ...cwdResolution,
@@ -34162,7 +34602,7 @@ function resolveRepoRootDetails(repoPath, options2 = {}) {
34162
34602
  const relativeBase = cwdIsCustomerWorktree ? cwdResolution.repoRoot : envCandidates[0]?.repoRoot ?? cwd;
34163
34603
  return {
34164
34604
  ...resolveExistingRoot(
34165
- path14.resolve(
34605
+ path15.resolve(
34166
34606
  explicitPathKind === "absolute" ? cwd : relativeBase,
34167
34607
  explicitRepoPath
34168
34608
  ),
@@ -34200,7 +34640,7 @@ function resolveFromEnvVars(cwd, env, envVars, options2 = {}) {
34200
34640
  for (const envVar of envVars) {
34201
34641
  const value = cleanWorkspacePath(env[envVar]);
34202
34642
  if (!value) continue;
34203
- const start = path14.resolve(cwd, value);
34643
+ const start = path15.resolve(cwd, value);
34204
34644
  if (existsSync5(start)) {
34205
34645
  const resolution = resolveExistingRoot(start, "environment", envVar);
34206
34646
  if (options2.skipBriefSource && looksLikeBriefSourceCheckout(resolution.repoRoot)) {
@@ -34213,7 +34653,7 @@ function resolveFromEnvVars(cwd, env, envVars, options2 = {}) {
34213
34653
  }
34214
34654
  function repoTargetHealth(resolution, options2 = {}) {
34215
34655
  const env = options2.env ?? process.env;
34216
- const cwd = path14.resolve(options2.cwd ?? process.cwd());
34656
+ const cwd = path15.resolve(options2.cwd ?? process.cwd());
34217
34657
  const gitRoot = runGit(resolution.repoRoot, ["rev-parse", "--show-toplevel"]);
34218
34658
  const evidence = [
34219
34659
  ...rootEvidence(resolution, cwd, env),
@@ -34274,7 +34714,7 @@ function repoTargetHealth(resolution, options2 = {}) {
34274
34714
  };
34275
34715
  }
34276
34716
  function resolveExistingRoot(start, source, envVar) {
34277
- const gitStart = existsSync5(start) && !statSync4(start).isDirectory() ? path14.dirname(start) : start;
34717
+ const gitStart = existsSync5(start) && !statSync4(start).isDirectory() ? path15.dirname(start) : start;
34278
34718
  const gitRoot = runGit(gitStart, ["rev-parse", "--show-toplevel"]);
34279
34719
  return {
34280
34720
  repoRoot: gitRoot && existsSync5(gitRoot) ? gitRoot : start,
@@ -34325,12 +34765,12 @@ function preferPinnedRepoRoot(env) {
34325
34765
  return env.BRIEF_REPO_ROOT_MODE === "pinned" || env.BRIEF_REPO_ROOT_PINNED === "true";
34326
34766
  }
34327
34767
  function looksLikeBriefSourceCheckout(repoRoot) {
34328
- if (!existsSync5(path14.join(repoRoot, "packages", "mcp", "src", "server.ts"))) {
34768
+ if (!existsSync5(path15.join(repoRoot, "packages", "mcp", "src", "server.ts"))) {
34329
34769
  return false;
34330
34770
  }
34331
34771
  try {
34332
34772
  const pkg = JSON.parse(
34333
- readFileSync3(path14.join(repoRoot, "package.json"), "utf8")
34773
+ readFileSync4(path15.join(repoRoot, "package.json"), "utf8")
34334
34774
  );
34335
34775
  return pkg.name === "brief";
34336
34776
  } catch {
@@ -34359,7 +34799,7 @@ var init_workspace = __esm({
34359
34799
  });
34360
34800
 
34361
34801
  // ../mcp/src/connectorReadiness.ts
34362
- import path15 from "node:path";
34802
+ import path16 from "node:path";
34363
34803
  function connectorReadiness(input) {
34364
34804
  const runtimeLabel = input.mode === "cli" ? "Brief CLI process" : "Brief MCP connector";
34365
34805
  const installStatusTool = input.mode === "cli" ? "install-status or brief.install_status" : "brief.install_status";
@@ -34453,8 +34893,8 @@ function connectorReadiness(input) {
34453
34893
  };
34454
34894
  }
34455
34895
  function briefSourceCheckoutMismatch(input) {
34456
- const repoRoot = input.repoRoot ? path15.resolve(input.repoRoot) : void 0;
34457
- const sourceRoot = input.freshness.sourceRoot ? path15.resolve(input.freshness.sourceRoot) : void 0;
34896
+ const repoRoot = input.repoRoot ? path16.resolve(input.repoRoot) : void 0;
34897
+ const sourceRoot = input.freshness.sourceRoot ? path16.resolve(input.freshness.sourceRoot) : void 0;
34458
34898
  if (!repoRoot || !sourceRoot) return void 0;
34459
34899
  if (samePath(repoRoot, sourceRoot)) return void 0;
34460
34900
  if (!looksLikeBriefSourceCheckout(repoRoot)) return void 0;
@@ -34469,7 +34909,7 @@ function briefSourceCheckoutMismatch(input) {
34469
34909
  };
34470
34910
  }
34471
34911
  function samePath(left, right) {
34472
- return path15.normalize(left) === path15.normalize(right);
34912
+ return path16.normalize(left) === path16.normalize(right);
34473
34913
  }
34474
34914
  var init_connectorReadiness = __esm({
34475
34915
  "../mcp/src/connectorReadiness.ts"() {
@@ -34603,8 +35043,8 @@ var init_connectorRecovery = __esm({
34603
35043
  });
34604
35044
 
34605
35045
  // ../mcp/src/serverFreshness.ts
34606
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync5 } from "node:fs";
34607
- import path16 from "node:path";
35046
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs";
35047
+ import path17 from "node:path";
34608
35048
  function mcpServerFreshness(options2) {
34609
35049
  const cwd = options2.cwd ?? process.cwd();
34610
35050
  const now = options2.now ?? /* @__PURE__ */ new Date();
@@ -34615,10 +35055,12 @@ function mcpServerFreshness(options2) {
34615
35055
  cwd,
34616
35056
  sourceRoot: options2.sourceRoot
34617
35057
  });
35058
+ const sourceKind = options2.distribution ?? connectorDistributionFor(options2.entrypoint);
34618
35059
  const watchedGlobs = watchedSourceGlobs();
34619
35060
  const base = {
34620
35061
  serverStartedAt: options2.serverStartedAt.toISOString(),
34621
35062
  checkedAt: now.toISOString(),
35063
+ sourceKind,
34622
35064
  process: {
34623
35065
  pid: process.pid,
34624
35066
  cwd,
@@ -34633,6 +35075,15 @@ function mcpServerFreshness(options2) {
34633
35075
  changedSinceStartCount: 0
34634
35076
  };
34635
35077
  if (!sourceRoot) {
35078
+ if (sourceKind === "package") {
35079
+ return {
35080
+ status: "current",
35081
+ ...base,
35082
+ action: freshnessAction("current"),
35083
+ summary: "The published Brief connector is running from an immutable package; source-checkout freshness checks are not applicable.",
35084
+ next: "Continue using Brief normally. Update the package when you want a newer connector release."
35085
+ };
35086
+ }
34636
35087
  return {
34637
35088
  status: "unknown",
34638
35089
  ...base,
@@ -34673,6 +35124,21 @@ function mcpServerFreshness(options2) {
34673
35124
  next: status === "restart_recommended" ? "Call brief.restart_connector from the coding agent, then call brief.install_status again and confirm serverFreshness.status is current." : "Continue using Brief normally."
34674
35125
  };
34675
35126
  }
35127
+ function connectorDistributionFor(entrypoint) {
35128
+ const configured = process.env.BRIEF_CONNECTOR_DISTRIBUTION;
35129
+ if (configured === "package" || configured === "source_checkout") {
35130
+ return configured;
35131
+ }
35132
+ const normalizedEntrypoint = path17.normalize(entrypoint);
35133
+ if (normalizedEntrypoint.includes(
35134
+ `${path17.sep}node_modules${path17.sep}runbrief${path17.sep}`
35135
+ ) || normalizedEntrypoint.includes(
35136
+ `${path17.sep}packages${path17.sep}runbrief${path17.sep}`
35137
+ )) {
35138
+ return "package";
35139
+ }
35140
+ return "unknown";
35141
+ }
34676
35142
  function freshnessAction(status) {
34677
35143
  if (status === "current") {
34678
35144
  return {
@@ -34707,30 +35173,30 @@ function findBriefSourceRoot(options2) {
34707
35173
  const candidates = [
34708
35174
  options2.sourceRoot,
34709
35175
  process.env.BRIEF_SOURCE_ROOT,
34710
- options2.entrypoint ? path16.dirname(options2.entrypoint) : void 0,
35176
+ options2.entrypoint ? path17.dirname(options2.entrypoint) : void 0,
34711
35177
  options2.cwd
34712
35178
  ].filter((candidate) => Boolean(candidate));
34713
35179
  for (const candidate of candidates) {
34714
- const root = walkUpForBriefRoot(path16.resolve(candidate));
35180
+ const root = walkUpForBriefRoot(path17.resolve(candidate));
34715
35181
  if (root) return root;
34716
35182
  }
34717
35183
  return void 0;
34718
35184
  }
34719
35185
  function walkUpForBriefRoot(start) {
34720
- let current = existsSync6(start) && statSafe(start)?.isFile() ? path16.dirname(start) : start;
35186
+ let current = existsSync6(start) && statSafe(start)?.isFile() ? path17.dirname(start) : start;
34721
35187
  while (true) {
34722
35188
  if (isBriefSourceRoot(current)) return current;
34723
- const parent = path16.dirname(current);
35189
+ const parent = path17.dirname(current);
34724
35190
  if (parent === current) return void 0;
34725
35191
  current = parent;
34726
35192
  }
34727
35193
  }
34728
35194
  function isBriefSourceRoot(candidate) {
34729
- const packageJsonPath = path16.join(candidate, "package.json");
35195
+ const packageJsonPath = path17.join(candidate, "package.json");
34730
35196
  if (!existsSync6(packageJsonPath)) return false;
34731
35197
  const packageJson = readJson(packageJsonPath);
34732
35198
  if (packageJson?.name !== "brief") return false;
34733
- return existsSync6(path16.join(candidate, "packages", "mcp")) && existsSync6(path16.join(candidate, "packages", "core")) && existsSync6(path16.join(candidate, "packages", "contracts"));
35199
+ return existsSync6(path17.join(candidate, "packages", "mcp")) && existsSync6(path17.join(candidate, "packages", "core")) && existsSync6(path17.join(candidate, "packages", "contracts"));
34734
35200
  }
34735
35201
  function watchedSourceGlobs() {
34736
35202
  return [
@@ -34768,7 +35234,7 @@ function collectWatchedFiles(sourceRoot, maxWatchedFiles) {
34768
35234
  truncated = true;
34769
35235
  break;
34770
35236
  }
34771
- const absolutePath = path16.join(sourceRoot, relativePath);
35237
+ const absolutePath = path17.join(sourceRoot, relativePath);
34772
35238
  const stats = statSafe(absolutePath);
34773
35239
  if (!stats) continue;
34774
35240
  if (stats.isDirectory()) {
@@ -34785,7 +35251,7 @@ function collectWatchedFiles(sourceRoot, maxWatchedFiles) {
34785
35251
  break;
34786
35252
  }
34787
35253
  files.push({
34788
- path: normalizePath(path16.relative(sourceRoot, absolutePath)),
35254
+ path: normalizePath(path17.relative(sourceRoot, absolutePath)),
34789
35255
  modifiedAtMs: stats.mtimeMs
34790
35256
  });
34791
35257
  }
@@ -34796,7 +35262,7 @@ function walkFiles2(sourceRoot, directory, files, maxWatchedFiles) {
34796
35262
  const entries = readdirSafe(directory);
34797
35263
  for (const entry of entries) {
34798
35264
  if (files.length >= maxWatchedFiles) return { truncated: true };
34799
- const absolutePath = path16.join(directory, entry.name);
35265
+ const absolutePath = path17.join(directory, entry.name);
34800
35266
  if (entry.isDirectory()) {
34801
35267
  if (shouldSkipDirectory(entry.name)) continue;
34802
35268
  const result = walkFiles2(
@@ -34812,7 +35278,7 @@ function walkFiles2(sourceRoot, directory, files, maxWatchedFiles) {
34812
35278
  const stats = statSafe(absolutePath);
34813
35279
  if (!stats) continue;
34814
35280
  files.push({
34815
- path: normalizePath(path16.relative(sourceRoot, absolutePath)),
35281
+ path: normalizePath(path17.relative(sourceRoot, absolutePath)),
34816
35282
  modifiedAtMs: stats.mtimeMs
34817
35283
  });
34818
35284
  }
@@ -34831,13 +35297,13 @@ function shouldSkipDirectory(name) {
34831
35297
  );
34832
35298
  }
34833
35299
  function shouldWatchFile(filePath) {
34834
- const base = path16.basename(filePath);
35300
+ const base = path17.basename(filePath);
34835
35301
  if (base.endsWith(".tsbuildinfo")) return false;
34836
35302
  if (base === ".DS_Store") return false;
34837
35303
  return true;
34838
35304
  }
34839
35305
  function normalizePath(value) {
34840
- return value.split(path16.sep).join("/");
35306
+ return value.split(path17.sep).join("/");
34841
35307
  }
34842
35308
  function statSafe(filePath) {
34843
35309
  try {
@@ -34855,7 +35321,7 @@ function readdirSafe(directory) {
34855
35321
  }
34856
35322
  function readJson(filePath) {
34857
35323
  try {
34858
- return JSON.parse(readFileSync4(filePath, "utf8"));
35324
+ return JSON.parse(readFileSync5(filePath, "utf8"));
34859
35325
  } catch {
34860
35326
  return void 0;
34861
35327
  }
@@ -34920,7 +35386,8 @@ var init_serverInstructions = __esm({
34920
35386
  briefMcpInstructions = [
34921
35387
  "Brief is the default repo-context, reuse, review, and handoff companion for non-trivial coding work. This is the normal path, not an optional reminder. Do not wait for the user to repeat 'use Brief'.",
34922
35388
  "At the first Brief-relevant task in a session, call brief.install_status once and confirm the repo target and connector freshness.",
34923
- "When the user asks for non-trivial work, call brief.next with the user's actual request before inspecting or editing files, then follow its compact workPacket and recommendation. Keep micro changes lightweight.",
35389
+ "Local mode is the default and does not require an account. Team mode is optional: it adds approved shared guidance through browser-authorized sync. If team sync is unavailable, continue local repo work and mention the team-sync limitation only when it affects the task.",
35390
+ "When the user asks for non-trivial work, call brief.next with the user's actual request before inspecting or editing files, read the returned compact workPacket, and follow its recommendation before editing. Keep micro changes lightweight.",
34924
35391
  "Before creating a helper, component, route, workflow, command, or skill, use the returned reuse candidates or call brief.reference_implementations and brief.codebase_map.",
34925
35392
  "Refresh Brief context when the task, files, risk, or validation plan changes.",
34926
35393
  "Before a meaningful handoff, call brief.review_diff. Save and verify a handoff only for multi-step, high-risk, or durable work.",
@@ -34968,6 +35435,8 @@ function compactInstallStatus(input) {
34968
35435
  const degraded = !blocked && (Boolean(cloudIssue) || input.readiness.verdict !== "agent_ready" || input.workflowWiring.status === "missing" || input.workflowWiring.status === "manual_workflow_only");
34969
35436
  const stats = input.scan.stats;
34970
35437
  const mcpContractActive = input.serverInstructionsActive && input.connectorReadiness.safeToUseBrief;
35438
+ const teamSyncStatus = input.cloudSync.configured ? input.teamContext.status === "failed" ? "unavailable" : input.teamContext.status === "fetched" ? "connected" : "checking" : "not_connected";
35439
+ const connectionMode = teamSyncStatus === "unavailable" ? "team_degraded" : teamSyncStatus === "connected" ? "team" : "local";
34971
35440
  const totalMapCandidates = stats.mapTotalCandidateCount ?? stats.mapNodeCount + (stats.mapOmittedCount ?? 0);
34972
35441
  const mapCoveragePercent = totalMapCandidates ? Math.round(stats.mapNodeCount / totalMapCandidates * 100) : 100;
34973
35442
  const repoGuidanceSignals = unique4(
@@ -34996,9 +35465,16 @@ function compactInstallStatus(input) {
34996
35465
  status: input.connectorReadiness.status,
34997
35466
  safeToUse: input.connectorReadiness.safeToUseBrief,
34998
35467
  fresh: input.serverFreshness.status === "current",
35468
+ distribution: input.serverFreshness.sourceKind,
34999
35469
  sourceRoot: input.serverFreshness.sourceRoot,
35000
35470
  summary: input.connectorReadiness.summary
35001
35471
  },
35472
+ connection: {
35473
+ mode: connectionMode,
35474
+ localAvailable: input.connectorReadiness.safeToUseBrief,
35475
+ teamSync: teamSyncStatus,
35476
+ summary: connectionMode === "team_degraded" ? "Local repo work is available. Team sync needs attention and will not block local analysis." : connectionMode === "team" ? "Local repo work and approved team context are available." : "Local repo work is available without an account. Team sync is optional."
35477
+ },
35002
35478
  cloud: {
35003
35479
  configured: input.cloudSync.configured,
35004
35480
  privacyMode: input.cloudSync.privacyMode,
@@ -35055,7 +35531,7 @@ var init_installStatus = __esm({
35055
35531
  "../mcp/src/installStatus.ts"() {
35056
35532
  "use strict";
35057
35533
  init_serverInstructions();
35058
- briefConnectorVersion = "0.1.2";
35534
+ briefConnectorVersion = "0.1.3";
35059
35535
  }
35060
35536
  });
35061
35537
 
@@ -35075,8 +35551,7 @@ function buildCompactStartEnvelope(input) {
35075
35551
  route: {
35076
35552
  tool: start.intent.tool,
35077
35553
  ...start.intent.recipe ? { recipe: start.intent.recipe } : {},
35078
- ...start.intent.autonomy ? { autonomy: start.intent.autonomy } : {},
35079
- ...start.intent.reviewBar ? { reviewBar: start.intent.reviewBar } : {}
35554
+ ...start.intent.autonomy ? { autonomy: start.intent.autonomy } : {}
35080
35555
  },
35081
35556
  workPacket: (() => {
35082
35557
  const packet = buildAgentWorkPacket({
@@ -35086,7 +35561,6 @@ function buildCompactStartEnvelope(input) {
35086
35561
  nextAction: input.next
35087
35562
  });
35088
35563
  return {
35089
- schemaVersion: packet.schemaVersion,
35090
35564
  compactText: packet.compactText
35091
35565
  };
35092
35566
  })(),
@@ -35241,15 +35715,15 @@ var init_startSummary = __esm({
35241
35715
  });
35242
35716
 
35243
35717
  // ../mcp/src/wikiInit.ts
35244
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
35245
- import path17 from "node:path";
35718
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
35719
+ import path18 from "node:path";
35246
35720
  function writeWikiInitArtifacts(repoRoot, wiki, options2) {
35247
- const wikiDir = path17.join(repoRoot, ".brief", "wiki");
35248
- const pagesDir = path17.join(wikiDir, "pages");
35249
- mkdirSync2(pagesDir, { recursive: true });
35721
+ const wikiDir = path18.join(repoRoot, ".brief", "wiki");
35722
+ const pagesDir = path18.join(wikiDir, "pages");
35723
+ mkdirSync3(pagesDir, { recursive: true });
35250
35724
  const pageArtifacts = wiki.pages.map((page2) => {
35251
- const pagePath = path17.join(pagesDir, `${wikiPageSlug(page2)}.md`);
35252
- writeFileSync5(pagePath, renderWikiPageMarkdown(wiki, page2));
35725
+ const pagePath = path18.join(pagesDir, `${wikiPageSlug(page2)}.md`);
35726
+ writeFileSync6(pagePath, renderWikiPageMarkdown(wiki, page2));
35253
35727
  return {
35254
35728
  id: page2.id,
35255
35729
  title: page2.title,
@@ -35258,32 +35732,32 @@ function writeWikiInitArtifacts(repoRoot, wiki, options2) {
35258
35732
  quality: page2.quality.status
35259
35733
  };
35260
35734
  });
35261
- const readmePath = path17.join(wikiDir, "README.md");
35262
- writeFileSync5(readmePath, renderWikiReadme(wiki, pageArtifacts));
35263
- const manifestPath = path17.join(wikiDir, "manifest.json");
35264
- writeFileSync5(
35735
+ const readmePath = path18.join(wikiDir, "README.md");
35736
+ writeFileSync6(readmePath, renderWikiReadme(wiki, pageArtifacts));
35737
+ const manifestPath = path18.join(wikiDir, "manifest.json");
35738
+ writeFileSync6(
35265
35739
  manifestPath,
35266
35740
  `${JSON.stringify(wikiInitManifest(wiki, pageArtifacts), null, 2)}
35267
35741
  `
35268
35742
  );
35269
- const workflowTemplatePath = path17.join(
35743
+ const workflowTemplatePath = path18.join(
35270
35744
  wikiDir,
35271
35745
  "brief-wiki-refresh.github-actions.yml"
35272
35746
  );
35273
- writeFileSync5(workflowTemplatePath, renderWikiRefreshWorkflow());
35747
+ writeFileSync6(workflowTemplatePath, renderWikiRefreshWorkflow());
35274
35748
  const agentFiles = options2.writeAgentFiles ? ["AGENTS.md", "CLAUDE.md"].map(
35275
35749
  (fileName) => upsertWikiAgentInstructions(repoRoot, fileName)
35276
35750
  ) : [];
35277
35751
  let githubWorkflow2;
35278
35752
  if (options2.writeGithubWorkflow) {
35279
- const workflowPath = path17.join(
35753
+ const workflowPath = path18.join(
35280
35754
  repoRoot,
35281
35755
  ".github",
35282
35756
  "workflows",
35283
35757
  "brief-wiki-refresh.yml"
35284
35758
  );
35285
- mkdirSync2(path17.dirname(workflowPath), { recursive: true });
35286
- writeFileSync5(workflowPath, renderWikiRefreshWorkflow({ active: true }));
35759
+ mkdirSync3(path18.dirname(workflowPath), { recursive: true });
35760
+ writeFileSync6(workflowPath, renderWikiRefreshWorkflow({ active: true }));
35287
35761
  githubWorkflow2 = {
35288
35762
  path: repoRelativePath(repoRoot, workflowPath),
35289
35763
  status: "written",
@@ -35473,12 +35947,12 @@ function wikiInitManifest(wiki, pageArtifacts) {
35473
35947
  };
35474
35948
  }
35475
35949
  function upsertWikiAgentInstructions(repoRoot, fileName) {
35476
- const filePath = path17.join(repoRoot, fileName);
35950
+ const filePath = path18.join(repoRoot, fileName);
35477
35951
  const status = existsSync7(filePath) ? "updated" : "created";
35478
- const existing = status === "updated" ? readFileSync5(filePath, "utf8") : "";
35952
+ const existing = status === "updated" ? readFileSync6(filePath, "utf8") : "";
35479
35953
  const initial = existing || `# ${fileName}
35480
35954
  `;
35481
- writeFileSync5(filePath, upsertMarkedBlock(initial, wikiAgentBlock()));
35955
+ writeFileSync6(filePath, upsertMarkedBlock(initial, wikiAgentBlock()));
35482
35956
  return {
35483
35957
  path: repoRelativePath(repoRoot, filePath),
35484
35958
  status,
@@ -35550,7 +36024,7 @@ function renderWikiRefreshWorkflow(options2 = {}) {
35550
36024
  " with:",
35551
36025
  " node-version: 22",
35552
36026
  " - name: Refresh wiki",
35553
- ' run: npx -y runbrief@0.1.2 wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files',
36027
+ ' run: npx -y runbrief@0.1.3 wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files',
35554
36028
  " - name: Open refresh PR",
35555
36029
  " uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11",
35556
36030
  " with:",
@@ -35571,7 +36045,7 @@ function markdownList(items) {
35571
36045
  return items.map((item) => `- ${item}`).join("\n");
35572
36046
  }
35573
36047
  function repoRelativePath(repoRoot, filePath) {
35574
- return path17.relative(repoRoot, filePath).split(path17.sep).join("/");
36048
+ return path18.relative(repoRoot, filePath).split(path18.sep).join("/");
35575
36049
  }
35576
36050
  function wikiReadmeRelativePath(repoRelativeFilePath) {
35577
36051
  return repoRelativeFilePath.replace(/^\.brief\/wiki\//, "");
@@ -35875,8 +36349,8 @@ var init_workflowWiringResponse = __esm({
35875
36349
 
35876
36350
  // ../mcp/src/server.ts
35877
36351
  var server_exports = {};
35878
- import { chmodSync, mkdirSync as mkdirSync3, writeFileSync as writeFileSync6 } from "node:fs";
35879
- import path18 from "node:path";
36352
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
36353
+ import path19 from "node:path";
35880
36354
  import { fileURLToPath } from "node:url";
35881
36355
  import {
35882
36356
  McpServer
@@ -36137,7 +36611,8 @@ function serverFreshness() {
36137
36611
  return mcpServerFreshness({
36138
36612
  serverStartedAt,
36139
36613
  entrypoint: serverModulePath,
36140
- mode: "server"
36614
+ mode: "server",
36615
+ distribution: connectorDistributionFor(serverModulePath)
36141
36616
  });
36142
36617
  }
36143
36618
  function summarizePacket(packet) {
@@ -36155,7 +36630,8 @@ function summarizePacket(packet) {
36155
36630
  sources: packet.sources,
36156
36631
  omitted: packet.omitted,
36157
36632
  ...packet.valueSignals ? { valueSignals: packet.valueSignals } : {},
36158
- ...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {}
36633
+ ...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {},
36634
+ ...packet.dependencyRemediation ? { dependencyRemediation: packet.dependencyRemediation } : {}
36159
36635
  };
36160
36636
  }
36161
36637
  function summarizeReview(review, detail = "auto") {
@@ -36593,6 +37069,7 @@ function summarizeBeforeYouCodeForStart(beforeYouCode) {
36593
37069
  decision: beforeYouCode.reuseFirst.decision,
36594
37070
  candidates: beforeYouCode.reuseFirst.candidates.slice(0, 2)
36595
37071
  },
37072
+ mapCoverage: beforeYouCode.mapCoverage,
36596
37073
  sharedContext: summarizeBeforeYouCodeSharedContext(
36597
37074
  beforeYouCode.teamGuidance
36598
37075
  ),
@@ -36656,7 +37133,7 @@ function summarizeWorkflowWiringForStart(wiring) {
36656
37133
  defaultAsk: wiring.defaultAsk,
36657
37134
  gaps: wiring.gaps.slice(0, 3),
36658
37135
  chatFeedback: wiring.chatFeedback.slice(0, 2),
36659
- finalRecapPolicy: wiring.finalRecapPolicy,
37136
+ finalRecapPolicy: compactFinalRecapPolicy(wiring.finalRecapPolicy),
36660
37137
  nextActions: wiring.nextActions.slice(0, 3)
36661
37138
  };
36662
37139
  }
@@ -36665,7 +37142,7 @@ function summarizeAutoEngageForStart(autoEngage) {
36665
37142
  status: autoEngage.status,
36666
37143
  summary: autoEngage.summary,
36667
37144
  chatFeedback: autoEngage.chatFeedback.slice(0, 1),
36668
- finalRecapPolicy: autoEngage.finalRecapPolicy,
37145
+ finalRecapPolicy: compactFinalRecapPolicy(autoEngage.finalRecapPolicy),
36669
37146
  triggers: autoEngage.triggerMatrix.slice(0, 3).map((trigger) => ({
36670
37147
  id: trigger.id,
36671
37148
  label: trigger.label,
@@ -36674,6 +37151,13 @@ function summarizeAutoEngageForStart(autoEngage) {
36674
37151
  nextActions: autoEngage.nextActions.slice(0, 2)
36675
37152
  };
36676
37153
  }
37154
+ function compactFinalRecapPolicy(policy) {
37155
+ return {
37156
+ maxLines: policy.maxLines,
37157
+ template: policy.defaultTemplate,
37158
+ fallbackWhenNotHelpful: policy.fallbackWhenNotHelpful
37159
+ };
37160
+ }
36677
37161
  function summarizeWiki(wiki) {
36678
37162
  return {
36679
37163
  wikiId: wiki.wikiId,
@@ -36726,21 +37210,22 @@ function summarizeLocalReceipt(receipt) {
36726
37210
  function writeHookFiles(repoRoot, files) {
36727
37211
  const written = [];
36728
37212
  for (const file of files) {
36729
- const targetPath = path18.join(repoRoot, file.path);
36730
- mkdirSync3(path18.dirname(targetPath), { recursive: true });
36731
- writeFileSync6(targetPath, file.body);
36732
- if (file.path.endsWith(".sh")) chmodSync(targetPath, 493);
37213
+ const targetPath = path19.join(repoRoot, file.path);
37214
+ mkdirSync4(path19.dirname(targetPath), { recursive: true });
37215
+ writeFileSync7(targetPath, file.body);
37216
+ if (file.path.endsWith(".sh")) chmodSync2(targetPath, 493);
36733
37217
  written.push(file.path);
36734
37218
  }
36735
37219
  return written;
36736
37220
  }
36737
- var server, personalizationKindSchema, referenceImplementationKindSchema, referenceImplementationStatusSchema, savePersonalizationToolSchema, workItemSchema, runtimeEvidenceSchema, handoffValidationSchema, artifactEvidenceSchema, loopCatalogFocusSchema, missionRecipeIdSchema2, startIntentSchema, nextStageSchema, responseDetailSchema, serverStartedAt, serverModulePath, repoTargetGuardExemptTools;
37221
+ var cloudBootstrap, server, personalizationKindSchema, referenceImplementationKindSchema, referenceImplementationStatusSchema, savePersonalizationToolSchema, workItemSchema, runtimeEvidenceSchema, handoffValidationSchema, artifactEvidenceSchema, loopCatalogFocusSchema, missionRecipeIdSchema2, startIntentSchema, nextStageSchema, responseDetailSchema, serverStartedAt, serverModulePath, repoTargetGuardExemptTools;
36738
37222
  var init_server = __esm({
36739
- "../mcp/src/server.ts"() {
37223
+ async "../mcp/src/server.ts"() {
36740
37224
  "use strict";
36741
37225
  init_dist();
36742
37226
  init_dist2();
36743
37227
  init_cloud();
37228
+ init_cloudCredentials();
36744
37229
  init_teamContext();
36745
37230
  init_missionInputOptions();
36746
37231
  init_connectorReadiness();
@@ -36756,10 +37241,15 @@ var init_server = __esm({
36756
37241
  init_workspace();
36757
37242
  init_handoffAutofill();
36758
37243
  init_scanFreshness();
37244
+ cloudBootstrap = await bootstrapCloudCredentials();
37245
+ if (cloudBootstrap.status === "failed") {
37246
+ console.error(`Brief cloud setup: ${cloudBootstrap.reason}`);
37247
+ process.exit(1);
37248
+ }
36759
37249
  server = new McpServer(
36760
37250
  {
36761
37251
  name: "brief",
36762
- version: "0.1.2"
37252
+ version: "0.1.3"
36763
37253
  },
36764
37254
  {
36765
37255
  instructions: briefMcpInstructions
@@ -37214,9 +37704,12 @@ var init_server = __esm({
37214
37704
  {
37215
37705
  repoPath: z2.string().optional().describe(
37216
37706
  "Path inside the repo. Defaults to BRIEF_REPO_ROOT captured from the agent workspace, then current working directory."
37707
+ ),
37708
+ detail: z2.enum(["compact", "expanded"]).optional().describe(
37709
+ "Use compact by default for the agent loop; request expanded only when diagnosing wiring or setup."
37217
37710
  )
37218
37711
  },
37219
- async ({ repoPath }) => {
37712
+ async ({ repoPath, detail }) => {
37220
37713
  const repoTargetInfo = resolveRepoTarget(repoPath);
37221
37714
  if (repoTargetInfo.repoTarget.status === "needs_repo_target") {
37222
37715
  return repoTargetBlock(repoTargetInfo);
@@ -37226,14 +37719,19 @@ var init_server = __esm({
37226
37719
  const skillLearning = buildRepoSkillLearningReport(repoRoot, {
37227
37720
  maxFiles: 6e3
37228
37721
  });
37722
+ const autoEngage = buildAgentAutoEngagementPlan(scan, skillLearning);
37723
+ const workflowWiring = evaluateAgentWorkflowWiring(scan);
37724
+ const responseDetail = detail ?? "compact";
37229
37725
  return jsonText({
37230
37726
  repoRoot,
37231
37727
  rootResolution,
37232
37728
  repoTarget,
37233
- autoEngage: buildAgentAutoEngagementPlan(scan, skillLearning),
37234
- workflowWiring: evaluateAgentWorkflowWiring(scan),
37729
+ detail: responseDetail,
37730
+ autoEngage: responseDetail === "expanded" ? autoEngage : summarizeAutoEngageForStart(autoEngage),
37731
+ workflowWiring: responseDetail === "expanded" ? workflowWiring : summarizeWorkflowWiringForStart(workflowWiring),
37235
37732
  skillLearning: summarizeSkillLearningForStart(skillLearning, void 0),
37236
- next: "Install the default instruction into always-on agent guidance, then run the smoke test from the connected coding agent to prove Brief stays engaged from natural task asks."
37733
+ next: responseDetail === "expanded" ? "Install the default instruction into always-on agent guidance, then run the smoke test from the connected coding agent to prove Brief stays engaged from natural task asks." : "Install the default instruction, then give the connected agent a normal task and confirm it calls brief.next before editing. Request detail: expanded only to diagnose wiring.",
37734
+ detailHint: 'Call brief.auto_engage with detail: "expanded" only when diagnosing setup or workflow wiring.'
37237
37735
  });
37238
37736
  }
37239
37737
  );
@@ -37352,7 +37850,7 @@ var init_server = __esm({
37352
37850
  skills: scan.stats.skillCount
37353
37851
  }
37354
37852
  },
37355
- next: teamContext.status === "fetched" ? "Use brief.context_for_task to route this team context into the next task." : "Set BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID to fetch approved team context from the Brief app."
37853
+ next: teamContext.status === "fetched" ? "Use brief.context_for_task to route this team context into the next task." : "Create a browser-authorized connection from the Brief app, then restart the agent. Approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID."
37356
37854
  });
37357
37855
  }
37358
37856
  );
@@ -38127,7 +38625,7 @@ var init_server = __esm({
38127
38625
  const sourceRepos = sourceRepoPaths && sourceRepoPaths.length > 0 ? sourceRepoPaths : [repoRoot];
38128
38626
  const inventories = sourceRepos.map(
38129
38627
  (sourceRepo) => buildAgentPackInventory(
38130
- path18.isAbsolute(sourceRepo) ? sourceRepo : path18.resolve(repoRoot, sourceRepo),
38628
+ path19.isAbsolute(sourceRepo) ? sourceRepo : path19.resolve(repoRoot, sourceRepo),
38131
38629
  maxFiles === void 0 ? {} : { maxFiles }
38132
38630
  )
38133
38631
  );
@@ -39502,6 +40000,7 @@ init_dist2();
39502
40000
  init_teamContext();
39503
40001
  init_missionInputOptions();
39504
40002
  init_cloud();
40003
+ init_cloudCredentials();
39505
40004
  init_workspace();
39506
40005
  init_connectorReadiness();
39507
40006
  init_connectorRecovery();
@@ -39513,15 +40012,139 @@ init_wikiInit();
39513
40012
  init_handoffAutofill();
39514
40013
  init_mapResponse();
39515
40014
  init_scanFreshness();
39516
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "node:fs";
39517
- import path19 from "node:path";
40015
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
40016
+ import path20 from "node:path";
39518
40017
  import { fileURLToPath as fileURLToPath2 } from "node:url";
40018
+
40019
+ // ../mcp/src/runner.ts
40020
+ init_cloud();
40021
+ import { hostname } from "node:os";
40022
+ import { spawn } from "node:child_process";
40023
+ async function runLoopRunner(options2, log = console.log) {
40024
+ const runnerId = options2.runnerId ?? `${hostname()}-${process.pid}`;
40025
+ const intervalMs = Math.max(options2.intervalMs ?? 4e3, 1e3);
40026
+ const env = options2.env ?? process.env;
40027
+ let didRun = false;
40028
+ do {
40029
+ const run = await claimRemoteLoopRun(runnerId, 180, env);
40030
+ if (!run) {
40031
+ if (options2.once) return;
40032
+ await sleep(intervalMs);
40033
+ continue;
40034
+ }
40035
+ didRun = true;
40036
+ log({ status: "running", runId: run.id, loop: run.loopName });
40037
+ try {
40038
+ const result = await executeAgentCommand(
40039
+ options2.agentCommand,
40040
+ run,
40041
+ options2.repoRoot,
40042
+ options2.commandTimeoutMs ?? 30 * 60 * 1e3
40043
+ );
40044
+ const proof = {
40045
+ runnerId,
40046
+ exitCode: result.exitCode,
40047
+ stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
40048
+ stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
40049
+ completedLocallyAt: (/* @__PURE__ */ new Date()).toISOString()
40050
+ };
40051
+ const updated = await updateRemoteLoopRun(run.id, {
40052
+ runnerId,
40053
+ status: result.exitCode === 0 ? "succeeded" : "failed",
40054
+ lastEvent: result.exitCode === 0 ? "The local agent completed the loop." : "The local agent exited with a failure.",
40055
+ resultSummary: compactOutput(result.stdout || result.stderr),
40056
+ proof,
40057
+ ...result.exitCode === 0 ? {} : { errorMessage: compactOutput(result.stderr || result.stdout) }
40058
+ }, env);
40059
+ log({ status: updated.status, runId: updated.id, proof });
40060
+ } catch (caught) {
40061
+ const message = caught instanceof Error ? caught.message : "Local runner failed.";
40062
+ try {
40063
+ const updated = await updateRemoteLoopRun(run.id, {
40064
+ runnerId,
40065
+ status: "failed",
40066
+ lastEvent: "The local runner could not execute the agent command.",
40067
+ errorMessage: message,
40068
+ proof: { runnerId, completedLocallyAt: (/* @__PURE__ */ new Date()).toISOString() }
40069
+ }, env);
40070
+ log({ status: updated.status, runId: updated.id, error: message });
40071
+ } catch (updateError) {
40072
+ log({ status: "failed", runId: run.id, error: message, updateError });
40073
+ throw new Error(
40074
+ `The runner could not persist the failed loop state: ${updateError instanceof Error ? updateError.message : "unknown update error"}`
40075
+ );
40076
+ }
40077
+ }
40078
+ } while (!options2.once || !didRun);
40079
+ }
40080
+ async function executeAgentCommand(command2, run, repoRoot, timeoutMs) {
40081
+ const prompt = [
40082
+ `You are running the approved Brief team loop: ${run.loopName}.`,
40083
+ `Run id: ${run.id}`,
40084
+ `Repository: ${run.repoFullName ?? repoRoot}`,
40085
+ "",
40086
+ "Approved loop guidance:",
40087
+ run.loopBody,
40088
+ "",
40089
+ "Guardrails:",
40090
+ ...run.guardrails.length > 0 ? run.guardrails.map((item) => `- ${item}`) : ["- Stop before external side effects without approval."],
40091
+ "",
40092
+ "Task:",
40093
+ run.task,
40094
+ "",
40095
+ "Return a concise summary of what you changed, validation run, proof, and remaining risk."
40096
+ ].join("\n");
40097
+ return await new Promise((resolve, reject) => {
40098
+ const child = spawn("/bin/sh", ["-lc", command2], {
40099
+ cwd: repoRoot,
40100
+ env: process.env,
40101
+ stdio: ["pipe", "pipe", "pipe"]
40102
+ });
40103
+ let stdout = "";
40104
+ let stderr = "";
40105
+ const timer = setTimeout(() => {
40106
+ child.kill("SIGTERM");
40107
+ reject(new Error(`Agent command timed out after ${Math.round(timeoutMs / 6e4)} minutes.`));
40108
+ }, timeoutMs);
40109
+ child.stdout.on("data", (chunk) => {
40110
+ stdout = `${stdout}${chunk.toString()}`.slice(-6e4);
40111
+ });
40112
+ child.stderr.on("data", (chunk) => {
40113
+ stderr = `${stderr}${chunk.toString()}`.slice(-2e4);
40114
+ });
40115
+ child.on("error", (error) => {
40116
+ clearTimeout(timer);
40117
+ reject(error);
40118
+ });
40119
+ child.on("close", (code) => {
40120
+ clearTimeout(timer);
40121
+ resolve({ exitCode: code ?? 1, stdout, stderr });
40122
+ });
40123
+ child.stdin.end(prompt);
40124
+ });
40125
+ }
40126
+ function compactOutput(value) {
40127
+ return value.trim().slice(-12e3) || "The agent returned no summary.";
40128
+ }
40129
+ function sleep(ms) {
40130
+ return new Promise((resolve) => setTimeout(resolve, ms));
40131
+ }
40132
+
40133
+ // ../mcp/src/cli.ts
39519
40134
  var args = process.argv.slice(2);
39520
40135
  var command = args[0];
39521
40136
  var cliStartedAt = /* @__PURE__ */ new Date();
39522
40137
  var cliModulePath = fileURLToPath2(import.meta.url);
40138
+ if (!isCloudLogoutCommand(command)) {
40139
+ const cloudBootstrap2 = await bootstrapCloudCredentials();
40140
+ if (cloudBootstrap2.status === "failed") {
40141
+ console.error(
40142
+ `Brief team sync is unavailable: ${cloudBootstrap2.reason} Continuing in local mode.`
40143
+ );
40144
+ }
40145
+ }
39523
40146
  if (!command || command === "--stdio" || command === "stdio" || command === "mcp") {
39524
- await Promise.resolve().then(() => (init_server(), server_exports));
40147
+ await init_server().then(() => server_exports);
39525
40148
  } else {
39526
40149
  await runCli(command, args.slice(1));
39527
40150
  }
@@ -39530,11 +40153,41 @@ async function runCli(commandName, commandArgs) {
39530
40153
  printJson(buildCliHelp(commandName));
39531
40154
  return;
39532
40155
  }
40156
+ if (isCloudLogoutCommand(commandName)) {
40157
+ forgetCloudCredentials();
40158
+ printJson({
40159
+ status: "cloud_credentials_removed",
40160
+ path: defaultCredentialsPath(),
40161
+ next: "Create a fresh browser-authorized connection from the Brief app before using team sync again."
40162
+ });
40163
+ return;
40164
+ }
39533
40165
  const rootResolution = resolveRepoRootDetails(
39534
40166
  option2(commandArgs, "repo") ?? option2(commandArgs, "repoPath") ?? option2(commandArgs, "repo-path")
39535
40167
  );
39536
40168
  const repoRoot = rootResolution.repoRoot;
39537
40169
  const repoTarget = repoTargetHealth(rootResolution);
40170
+ if (isCommand(commandName, "runner", "loop-runner", "run-loop")) {
40171
+ const agentCommand = option2(commandArgs, "agent-command") ?? process.env.BRIEF_AGENT_COMMAND;
40172
+ if (!agentCommand) {
40173
+ fail(
40174
+ "Missing --agent-command. Set BRIEF_AGENT_COMMAND to the local Claude, Codex, or compatible agent command."
40175
+ );
40176
+ }
40177
+ const runnerOptions = {
40178
+ repoRoot,
40179
+ agentCommand,
40180
+ once: hasFlag(commandArgs, "once")
40181
+ };
40182
+ const runnerId = option2(commandArgs, "runner-id");
40183
+ const intervalMs = numberOption(commandArgs, "interval-ms");
40184
+ const commandTimeoutMs = numberOption(commandArgs, "timeout-ms");
40185
+ if (runnerId) runnerOptions.runnerId = runnerId;
40186
+ if (intervalMs) runnerOptions.intervalMs = intervalMs;
40187
+ if (commandTimeoutMs) runnerOptions.commandTimeoutMs = commandTimeoutMs;
40188
+ await runLoopRunner(runnerOptions);
40189
+ return;
40190
+ }
39538
40191
  if (isCommand(commandName, "install-status", "install_status")) {
39539
40192
  const freshness = cliServerFreshness();
39540
40193
  const connector = connectorReadiness({
@@ -40200,7 +40853,7 @@ async function runCli(commandName, commandArgs) {
40200
40853
  const sourceRepos = filesOption2(commandArgs, "source-repos") ?? filesOption2(commandArgs, "sourceRepos") ?? filesOption2(commandArgs, "sources") ?? [repoRoot];
40201
40854
  const inventories = sourceRepos.map(
40202
40855
  (sourceRepo) => buildAgentPackInventory(
40203
- path19.isAbsolute(sourceRepo) ? sourceRepo : path19.resolve(repoRoot, sourceRepo),
40856
+ path20.isAbsolute(sourceRepo) ? sourceRepo : path20.resolve(repoRoot, sourceRepo),
40204
40857
  maxFiles === void 0 ? {} : { maxFiles }
40205
40858
  )
40206
40859
  );
@@ -41072,7 +41725,7 @@ async function runCli(commandName, commandArgs) {
41072
41725
  skills: scan.stats.skillCount
41073
41726
  }
41074
41727
  },
41075
- next: teamContext.status === "fetched" ? "Use context_for_task to route this team context into the next task." : "Set BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID to fetch approved team context from the Brief app."
41728
+ next: teamContext.status === "fetched" ? "Use context_for_task to route this team context into the next task." : "Create a browser-authorized connection from the Brief app, then restart the agent. Approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID."
41076
41729
  });
41077
41730
  return;
41078
41731
  }
@@ -41269,6 +41922,11 @@ async function runCli(commandName, commandArgs) {
41269
41922
  function isCommand(commandName, ...aliases) {
41270
41923
  return aliases.includes(commandName);
41271
41924
  }
41925
+ function isCloudLogoutCommand(commandName) {
41926
+ return Boolean(
41927
+ commandName && ["cloud-logout", "cloud_logout", "disconnect-cloud"].includes(commandName)
41928
+ );
41929
+ }
41272
41930
  function wikiInitMode(argsList) {
41273
41931
  if (hasFlag(argsList, "init")) return "init";
41274
41932
  if (hasFlag(argsList, "update")) return "update";
@@ -41352,6 +42010,33 @@ function cliCommandHelp() {
41352
42010
  "After changing Brief source to confirm the running connector is fresh."
41353
42011
  ]
41354
42012
  },
42013
+ {
42014
+ command: "cloud-logout",
42015
+ aliases: ["cloud_logout", "disconnect-cloud"],
42016
+ purpose: "Remove the locally stored cloud credential so the next setup can pair a device again.",
42017
+ repoBound: false,
42018
+ usage: "brief cloud-logout",
42019
+ examples: ["brief cloud-logout"],
42020
+ whenToUse: [
42021
+ "When a device needs to be disconnected from a Brief workspace.",
42022
+ "When a one-time browser pairing has expired before the connector started."
42023
+ ]
42024
+ },
42025
+ {
42026
+ command: "runner",
42027
+ aliases: ["loop-runner", "run-loop"],
42028
+ purpose: "Poll for an approved team loop and execute it with the developer's local agent command.",
42029
+ repoBound: true,
42030
+ usage: 'runbrief runner --repo $PWD --agent-command "claude -p"',
42031
+ examples: [
42032
+ 'runbrief runner --repo $PWD --agent-command "claude -p"',
42033
+ 'runbrief runner --repo $PWD --agent-command "codex exec" --once'
42034
+ ],
42035
+ whenToUse: [
42036
+ "After pairing a connector from the Brief app.",
42037
+ "When a team-approved loop is queued in the web app."
42038
+ ]
42039
+ },
41355
42040
  {
41356
42041
  command: "next",
41357
42042
  aliases: ["brief-next", "brief_next"],
@@ -41697,7 +42382,11 @@ function positionalText(argsList) {
41697
42382
  "owner",
41698
42383
  "receipts",
41699
42384
  "added-by",
41700
- "addedBy"
42385
+ "addedBy",
42386
+ "agent-command",
42387
+ "runner-id",
42388
+ "interval-ms",
42389
+ "timeout-ms"
41701
42390
  ]);
41702
42391
  const parts = [];
41703
42392
  for (let index = 0; index < argsList.length; index += 1) {
@@ -41953,7 +42642,7 @@ function artifactEvidenceFromOptions(argsList) {
41953
42642
  );
41954
42643
  const evidence = [];
41955
42644
  for (const raw of rawEvidenceValues) {
41956
- const json = raw.startsWith("@") ? readFileSync6(path19.resolve(raw.slice(1)), "utf8") : raw;
42645
+ const json = raw.startsWith("@") ? readFileSync7(path20.resolve(raw.slice(1)), "utf8") : raw;
41957
42646
  try {
41958
42647
  const parsed = JSON.parse(json);
41959
42648
  const items = Array.isArray(parsed) ? parsed : [parsed];
@@ -42031,7 +42720,7 @@ function missionSummaryFromLocalReceipt2(repoRoot, missionReceiptId) {
42031
42720
  function runtimeEvidencePacketFromOptions(argsList) {
42032
42721
  const raw = option2(argsList, "runtime-packet") ?? option2(argsList, "runtime-evidence-packet") ?? option2(argsList, "runtimeEvidencePacket");
42033
42722
  if (!raw) return void 0;
42034
- const json = raw.startsWith("@") ? readFileSync6(path19.resolve(raw.slice(1)), "utf8") : raw;
42723
+ const json = raw.startsWith("@") ? readFileSync7(path20.resolve(raw.slice(1)), "utf8") : raw;
42035
42724
  try {
42036
42725
  return cleanRuntimeEvidencePacket2(
42037
42726
  runtimeEvidencePacketSchema.parse(JSON.parse(json))
@@ -42200,6 +42889,15 @@ function printContextSummary(packet, localReceipt, teamContext) {
42200
42889
  teamContextLine(teamContext),
42201
42890
  teamGuidanceLine(packet),
42202
42891
  valueSignalLine(packet),
42892
+ ...packet.dependencyRemediation ? [
42893
+ "",
42894
+ "Dependency plan:",
42895
+ `- ${packet.dependencyRemediation.packageManager} \xB7 ${packet.dependencyRemediation.status}`,
42896
+ `- manifests: ${packet.dependencyRemediation.manifestFiles.slice(0, 4).join(", ") || "none found"}`,
42897
+ `- lockfiles: ${packet.dependencyRemediation.lockfiles.slice(0, 3).join(", ") || "none found"}`,
42898
+ `- packages: ${packet.dependencyRemediation.packageSignals.map((item) => item.name).join(", ") || "need advisory/scanner evidence"}`,
42899
+ `- graph proof: ${packet.dependencyRemediation.graphCommands.slice(0, 2).join(", ") || "name the direct/transitive path"}`
42900
+ ] : [],
42203
42901
  "",
42204
42902
  "Rules:",
42205
42903
  ...packet.rules.length > 0 ? packet.rules.slice(0, 8).map((rule) => `- [${rule.enforcement}] ${rule.title}`) : ["- none"],
@@ -42252,10 +42950,10 @@ function valueSignalLine(packet) {
42252
42950
  function writeHookFiles2(repoRoot, files) {
42253
42951
  const written = [];
42254
42952
  for (const file of files) {
42255
- const targetPath = path19.join(repoRoot, file.path);
42256
- mkdirSync4(path19.dirname(targetPath), { recursive: true });
42257
- writeFileSync7(targetPath, file.body);
42258
- if (file.path.endsWith(".sh")) chmodSync2(targetPath, 493);
42953
+ const targetPath = path20.join(repoRoot, file.path);
42954
+ mkdirSync5(path20.dirname(targetPath), { recursive: true });
42955
+ writeFileSync8(targetPath, file.body);
42956
+ if (file.path.endsWith(".sh")) chmodSync3(targetPath, 493);
42259
42957
  written.push(file.path);
42260
42958
  }
42261
42959
  return written;
@@ -42383,7 +43081,8 @@ function cliServerFreshness() {
42383
43081
  return mcpServerFreshness({
42384
43082
  serverStartedAt: cliStartedAt,
42385
43083
  entrypoint: cliModulePath,
42386
- mode: "cli"
43084
+ mode: "cli",
43085
+ distribution: connectorDistributionFor(cliModulePath)
42387
43086
  });
42388
43087
  }
42389
43088
  function printJson(value) {
@@ -42407,7 +43106,7 @@ function nextScanStep(cloudSync) {
42407
43106
  if (cloudSync.status === "failed") {
42408
43107
  return `The local scan succeeded, but the web app did not update because cloud sync failed: ${cloudSync.reason}. Fix cloud sync before trusting hosted freshness; refresh local wiki separately with brief.initialize_repo_wiki update: true.`;
42409
43108
  }
42410
- return "The local scan succeeded, but the web app did not update. Set BRIEF_API_URL, BRIEF_API_KEY, BRIEF_WORKSPACE_ID, and BRIEF_PRIVACY_MODE=cloud-sync-derived to sync scans to Brief. To refresh repo-local wiki docs, ask the agent to call brief.initialize_repo_wiki with update: true.";
43109
+ return "The local scan succeeded, but the web app did not update. Create a browser-authorized connection from the Brief app and restart the agent; approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID. To refresh repo-local wiki docs, ask the agent to call brief.initialize_repo_wiki with update: true.";
42411
43110
  }
42412
43111
  function fail(message) {
42413
43112
  process.stderr.write(`${message}