runbrief 0.1.3 → 0.1.4

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 +9 -4
  2. package/dist/cli.js +1061 -99
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -765,6 +765,7 @@ var init_dist = __esm({
765
765
  "small_refactor",
766
766
  "bug_investigation",
767
767
  "qa_edge_cases",
768
+ "web_dogfood",
768
769
  "performance",
769
770
  "porting",
770
771
  "experiment",
@@ -5472,12 +5473,12 @@ function sourceReadLimit(type) {
5472
5473
  return CODE_SOURCE_MAX_BYTES;
5473
5474
  return void 0;
5474
5475
  }
5475
- function sourceDocument(pathName, type, content) {
5476
+ function sourceDocument(pathName2, type, content) {
5476
5477
  return {
5477
- id: idFor("src", `${pathName}:${content}`),
5478
- path: pathName,
5478
+ id: idFor("src", `${pathName2}:${content}`),
5479
+ path: pathName2,
5479
5480
  type,
5480
- title: titleFromPath(pathName),
5481
+ title: titleFromPath(pathName2),
5481
5482
  excerpt: firstLines(content, 14),
5482
5483
  content,
5483
5484
  contentHash: contentHash(content),
@@ -7622,12 +7623,12 @@ function extractReviewMemories(sources, rules) {
7622
7623
  }
7623
7624
  return memories.slice(0, 32);
7624
7625
  }
7625
- function mapNode(type, pathName, summary, tags, metadata) {
7626
+ function mapNode(type, pathName2, summary, tags, metadata) {
7626
7627
  const node = {
7627
- id: idFor("map", `${type}:${pathName}`),
7628
+ id: idFor("map", `${type}:${pathName2}`),
7628
7629
  type,
7629
- name: titleFromPath(pathName),
7630
- path: pathName,
7630
+ name: titleFromPath(pathName2),
7631
+ path: pathName2,
7631
7632
  summary,
7632
7633
  tags
7633
7634
  };
@@ -13473,8 +13474,8 @@ function sameStem(testPath, sourcePath) {
13473
13474
  function isTestScriptNode(node) {
13474
13475
  return node.type === "workflow" && node.tags.some((tag) => /test|check|type|e2e|smoke/i.test(tag));
13475
13476
  }
13476
- function isTestPathCandidate(pathName) {
13477
- return /(^|\/|\.)(test|spec)\.[^.]+$/i.test(pathName);
13477
+ function isTestPathCandidate(pathName2) {
13478
+ return /(^|\/|\.)(test|spec)\.[^.]+$/i.test(pathName2);
13478
13479
  }
13479
13480
  function summarizeContext(task, rules, tests, map = []) {
13480
13481
  const implementationPaths = contextSummaryPaths(map);
@@ -14021,6 +14022,331 @@ var init_context = __esm({
14021
14022
  }
14022
14023
  });
14023
14024
 
14025
+ // ../core/dist/productIntelligence.js
14026
+ import { isIP } from "node:net";
14027
+ function loadProductIntelligence(repoRoot) {
14028
+ for (const filePath of briefCacheCandidatePaths(repoRoot, productIntelligenceFile)) {
14029
+ const content = readBriefLedgerFile(filePath);
14030
+ if (!content)
14031
+ continue;
14032
+ try {
14033
+ const parsed = JSON.parse(content);
14034
+ if (isProductIntelligenceStore(parsed)) {
14035
+ return withLocalProductLearnings(repoRoot, parsed);
14036
+ }
14037
+ } catch {
14038
+ }
14039
+ }
14040
+ return withLocalProductLearnings(repoRoot, emptyProductIntelligence(pathName(repoRoot)));
14041
+ }
14042
+ function saveProductIntelligence(repoRoot, input) {
14043
+ return withBriefCacheLock(repoRoot, "product-intelligence", () => {
14044
+ const current = loadProductIntelligence(repoRoot);
14045
+ const now = (/* @__PURE__ */ new Date()).toISOString();
14046
+ const facts = [...current.facts];
14047
+ const savedFactIds = [];
14048
+ for (const candidate of input.facts) {
14049
+ const statement = normalizeText(candidate.statement, 1e3);
14050
+ if (!statement)
14051
+ continue;
14052
+ const sources = (candidate.sources ?? []).map((source) => normalizeSource(source, now));
14053
+ const existing = facts.find((fact2) => fact2.kind === candidate.kind && normalizeKey(fact2.statement) === normalizeKey(statement));
14054
+ if (existing) {
14055
+ existing.appliesTo = unique([
14056
+ ...existing.appliesTo,
14057
+ ...candidate.appliesTo ?? []
14058
+ ]).slice(0, 24);
14059
+ existing.sources = mergeSources(existing.sources, sources);
14060
+ existing.confidence = Math.max(existing.confidence, clampConfidence(candidate.confidence ?? existing.confidence));
14061
+ if (candidate.status)
14062
+ existing.status = candidate.status;
14063
+ existing.updatedAt = now;
14064
+ savedFactIds.push(existing.id);
14065
+ continue;
14066
+ }
14067
+ const fact = {
14068
+ id: idFor("product-fact", `${candidate.kind}:${statement}`),
14069
+ kind: candidate.kind,
14070
+ statement,
14071
+ appliesTo: unique(candidate.appliesTo ?? []).slice(0, 24),
14072
+ status: candidate.status ?? "proposed",
14073
+ confidence: clampConfidence(candidate.confidence ?? 0.65),
14074
+ sources,
14075
+ createdAt: now,
14076
+ updatedAt: now
14077
+ };
14078
+ facts.unshift(fact);
14079
+ savedFactIds.push(fact.id);
14080
+ }
14081
+ const store = {
14082
+ version: 1,
14083
+ repoName: input.repoName?.trim() || current.repoName,
14084
+ updatedAt: now,
14085
+ facts: facts.slice(0, 300),
14086
+ openQuestions: unique([
14087
+ ...current.openQuestions,
14088
+ ...input.openQuestions ?? []
14089
+ ]).map((item) => normalizeText(item, 300)).filter(Boolean).slice(0, 40),
14090
+ decisions: unique([...current.decisions, ...input.decisions ?? []]).map((item) => normalizeText(item, 500)).filter(Boolean).slice(0, 80)
14091
+ };
14092
+ writeBriefCacheFile(repoRoot, productIntelligenceFile, `${JSON.stringify(store, null, 2)}
14093
+ `);
14094
+ return { store, savedFactIds };
14095
+ });
14096
+ }
14097
+ function buildProductIntelligenceContext(store, task, maxLines = 14) {
14098
+ const now = Date.now();
14099
+ const ranked = store.facts.filter((fact) => fact.status !== "rejected").map((fact) => ({
14100
+ fact,
14101
+ stale: isStale(fact, now),
14102
+ score: factRelevance(fact, task)
14103
+ })).sort((a, b) => b.score - a.score || Number(b.fact.status === "approved") - Number(a.fact.status === "approved") || b.fact.updatedAt.localeCompare(a.fact.updatedAt));
14104
+ const relevant = ranked.filter((item) => item.score > 0);
14105
+ const selected = (relevant.length > 0 ? relevant : ranked).slice(0, 8);
14106
+ const relevantFacts = selected.map((item) => item.fact);
14107
+ const staleFactCount = ranked.filter((item) => item.stale).length;
14108
+ const sourceCount = new Set(store.facts.flatMap((fact) => fact.sources.map((source) => source.id))).size;
14109
+ const hasApproved = relevantFacts.some((fact) => fact.status === "approved");
14110
+ const hasProposed = relevantFacts.some((fact) => fact.status === "proposed");
14111
+ const status = relevantFacts.length === 0 ? "needs_research" : hasProposed ? "needs_review" : hasApproved && staleFactCount === 0 ? "ready" : "needs_research";
14112
+ const summary = relevantFacts.length === 0 ? "No product intelligence matches this task yet." : `${relevantFacts.length} product fact${relevantFacts.length === 1 ? "" : "s"} matched; ${sourceCount} cited source${sourceCount === 1 ? "" : "s"}${staleFactCount > 0 ? `, ${staleFactCount} may be stale` : ""}.`;
14113
+ const compactLines = [
14114
+ `Product intelligence: ${summary}`,
14115
+ ...relevantFacts.slice(0, 5).map((fact) => `- ${statusLabel(fact.status)} ${fact.kind}: ${fact.statement}`),
14116
+ ...store.openQuestions.length > 0 ? [`Open question: ${store.openQuestions[0]}`] : [],
14117
+ ...staleFactCount > 0 ? ["Refresh market, competitor, or integration evidence before treating it as current."] : []
14118
+ ];
14119
+ const compactText = compactLines.slice(0, Math.max(4, maxLines)).join("\n");
14120
+ return {
14121
+ status,
14122
+ summary,
14123
+ relevantFacts,
14124
+ staleFactCount,
14125
+ sourceCount,
14126
+ openQuestions: store.openQuestions.slice(0, 8),
14127
+ compactText,
14128
+ next: status === "needs_research" ? "Run brief.product_research_plan, use approved public sources where needed, then save cited observations as proposed context." : status === "needs_review" ? "Review proposed product facts with human judgment before treating them as team defaults." : "Use the cited product facts alongside repo rules; do not let market evidence override a direct user or team decision."
14129
+ };
14130
+ }
14131
+ function buildProductResearchPlan(input) {
14132
+ const task = normalizeText(input.task, 1e3);
14133
+ const lower = task.toLowerCase();
14134
+ const audience = input.audience?.trim() || "the target user";
14135
+ const competitors = unique(input.competitors ?? []).slice(0, 8);
14136
+ const integrations = unique(input.integrations ?? []).slice(0, 8);
14137
+ const wantsTaste = /design|copy|content|microcopy|ux|ui|tone|visual|style/.test(lower);
14138
+ const wantsMarket = competitors.length > 0 || /competitor|market|alternative|position|pricing|category|landscape/.test(lower);
14139
+ const wantsIntegrations = integrations.length > 0 || /integration|connector|mcp|github|linear|jira|sentry|datadog|slack|figma/.test(lower);
14140
+ const focus = unique([
14141
+ "user job and desired outcome",
14142
+ ...wantsTaste ? ["interaction and language taste"] : [],
14143
+ ...wantsMarket ? ["category alternatives and differentiation"] : [],
14144
+ ...wantsIntegrations ? ["integration expectations and trust boundaries"] : [],
14145
+ "repo and team evidence that should constrain the decision"
14146
+ ]);
14147
+ const questions = [
14148
+ `What is ${audience} trying to accomplish, and what makes the current path hard?`,
14149
+ "Which existing behavior, workflow, or language should Brief preserve rather than reinvent?",
14150
+ ...wantsTaste ? [
14151
+ "What interaction, information hierarchy, and language patterns do users already recognize as high quality?",
14152
+ "Which patterns are genuinely useful versus decorative or over-automated?"
14153
+ ] : [],
14154
+ ...wantsMarket ? [
14155
+ "What do credible alternatives promise, and where do users still have to do the hard judgment themselves?",
14156
+ "What narrow outcome can Brief own without copying a competitor's category language?"
14157
+ ] : [],
14158
+ ...wantsIntegrations ? [
14159
+ "What setup, auth, permission, and failure behavior do users expect from this integration?",
14160
+ "Which integration signals are official and current enough to become product guidance?"
14161
+ ] : []
14162
+ ];
14163
+ const searchQueries = [
14164
+ `${task} official documentation workflow users`,
14165
+ ...competitors.length > 0 ? competitors.map((name) => `${name} official product docs ${task}`) : ["leading alternatives official docs product workflow"],
14166
+ ...integrations.length > 0 ? integrations.map((name) => `${name} official integration docs auth permissions`) : []
14167
+ ].slice(0, 10);
14168
+ return {
14169
+ status: "plan_ready",
14170
+ task,
14171
+ focus,
14172
+ questions: questions.slice(0, 8),
14173
+ searchQueries,
14174
+ sourcePolicy: [
14175
+ "Prefer official product documentation, changelogs, pricing, and security pages for current capabilities.",
14176
+ "Use user feedback, interviews, support, analytics, and dogfood receipts for needs and outcomes; label them separately from market claims.",
14177
+ "Attach the URL or repo path and access date to every saved observation; include a short excerpt only when it clarifies the claim.",
14178
+ "Save findings as proposed until a product owner approves them. Conflicts stay visible as open questions.",
14179
+ "Do not send private customer material or credentials to external research sources."
14180
+ ],
14181
+ deliverable: [
14182
+ "3-7 cited observations, each with a claim and implication for Brief.",
14183
+ "A short JTBD/persona or taste decision, if the evidence supports one.",
14184
+ "Competitor or integration notes separated from direct user evidence.",
14185
+ "Open questions and a decision owner rather than false certainty."
14186
+ ],
14187
+ next: "Research with the host's approved web/browser tools, then call brief.save_product_context with cited proposed facts."
14188
+ };
14189
+ }
14190
+ function emptyProductIntelligence(repoName) {
14191
+ return {
14192
+ version: 1,
14193
+ repoName,
14194
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
14195
+ facts: [],
14196
+ openQuestions: [],
14197
+ decisions: []
14198
+ };
14199
+ }
14200
+ function withLocalProductLearnings(repoRoot, store) {
14201
+ const learnedFacts = loadLocalLearnings(repoRoot).filter((learning) => /product|design|content|copy|ux|ui|user|journey|taste|terminology|workflow|integration|competitor|market/i.test(`${learning.title} ${learning.body} ${learning.files.join(" ")}`)).slice(0, 80).map((learning) => ({
14202
+ id: idFor("product-learning", learning.id),
14203
+ kind: /copy|content|terminology|tone/i.test(`${learning.title} ${learning.body}`) ? "terminology" : /user|journey|job/i.test(`${learning.title} ${learning.body}`) ? "jtbd" : "principle",
14204
+ statement: normalizeText(learning.body, 1e3),
14205
+ appliesTo: learning.files,
14206
+ status: learning.status === "approved" ? "approved" : "proposed",
14207
+ confidence: clampConfidence(learning.confidence),
14208
+ sources: [
14209
+ {
14210
+ id: idFor("product-learning-source", learning.id),
14211
+ kind: "feedback",
14212
+ label: `Local learning: ${normalizeText(learning.title, 140)}`,
14213
+ path: ".brief-cache/learnings.json",
14214
+ accessedAt: learning.createdAt
14215
+ }
14216
+ ],
14217
+ createdAt: learning.createdAt,
14218
+ updatedAt: learning.createdAt
14219
+ }));
14220
+ const existingIds = new Set(store.facts.map((fact) => fact.id));
14221
+ const nextFacts = learnedFacts.filter((fact) => !existingIds.has(fact.id));
14222
+ return nextFacts.length === 0 ? store : { ...store, facts: [...nextFacts, ...store.facts].slice(0, 300) };
14223
+ }
14224
+ function pathName(repoRoot) {
14225
+ return repoRoot.split(/[\\/]/).filter(Boolean).pop() ?? "repo";
14226
+ }
14227
+ function factRelevance(fact, task) {
14228
+ const text = `${task} ${fact.appliesTo.join(" ")}`.toLowerCase();
14229
+ const statement = fact.statement.toLowerCase();
14230
+ const words = unique(text.split(/[^a-z0-9]+/).filter((word) => word.length >= 4));
14231
+ let score = fact.status === "approved" ? 3 : 1;
14232
+ for (const word of words)
14233
+ if (statement.includes(word))
14234
+ score += 4;
14235
+ const kindTerms = {
14236
+ persona: ["user", "audience", "persona", "customer"],
14237
+ jtbd: ["job", "outcome", "workflow", "task"],
14238
+ principle: ["design", "ui", "ux", "copy", "taste", "quality"],
14239
+ terminology: ["copy", "content", "word", "term", "language", "tone"],
14240
+ competitor: ["competitor", "market", "alternative", "position"],
14241
+ integration: ["integration", "connector", "auth", "permission", "mcp"],
14242
+ metric: ["metric", "activation", "retention", "conversion", "usage"],
14243
+ decision: ["decision", "scope", "choose", "prioritize"]
14244
+ };
14245
+ if (kindTerms[fact.kind].some((term) => text.includes(term)))
14246
+ score += 8;
14247
+ return score;
14248
+ }
14249
+ function isStale(fact, now) {
14250
+ const ageDays = (now - Date.parse(fact.updatedAt)) / 864e5;
14251
+ const marketFact = ["competitor", "integration", "metric"].includes(fact.kind);
14252
+ return !Number.isFinite(ageDays) || ageDays > (marketFact ? staleMarketDays : staleProductDays);
14253
+ }
14254
+ function statusLabel(status) {
14255
+ return status === "approved" ? "[approved]" : "[proposed]";
14256
+ }
14257
+ function normalizeSource(input, fallbackAccessedAt) {
14258
+ const url = input.url?.trim();
14259
+ if (url)
14260
+ assertPublicResearchUrl(url);
14261
+ if (!input.path?.trim() && !url && input.kind !== "user" && input.kind !== "feedback" && input.kind !== "dogfood") {
14262
+ throw new Error(`A ${input.kind} source needs a public URL or repo path.`);
14263
+ }
14264
+ const label = normalizeText(input.label, 180);
14265
+ return {
14266
+ id: idFor("product-source", `${input.kind}:${url ?? input.path ?? label}`),
14267
+ kind: input.kind,
14268
+ label,
14269
+ ...url ? { url } : {},
14270
+ ...input.path?.trim() ? { path: normalizeText(input.path, 300) } : {},
14271
+ accessedAt: input.accessedAt ?? fallbackAccessedAt,
14272
+ ...input.excerpt?.trim() ? { excerpt: normalizeText(input.excerpt, 420) } : {}
14273
+ };
14274
+ }
14275
+ function assertPublicResearchUrl(value) {
14276
+ let parsed;
14277
+ try {
14278
+ parsed = new URL(value);
14279
+ } catch {
14280
+ throw new Error("Product research URLs must be valid HTTPS URLs.");
14281
+ }
14282
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
14283
+ throw new Error("Product research URLs must use HTTPS and cannot include credentials.");
14284
+ }
14285
+ const host = parsed.hostname.toLowerCase();
14286
+ if (host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "::1" || isPrivateIp(host)) {
14287
+ throw new Error("Product research URLs must point to public sources.");
14288
+ }
14289
+ }
14290
+ function isPrivateIp(host) {
14291
+ const family = isIP(host);
14292
+ if (family === 4) {
14293
+ const octets = host.split(".").map(Number);
14294
+ return octets[0] === 10 || octets[0] === 127 || octets[0] === 169 && octets[1] === 254 || octets[0] === 172 && (octets[1] ?? -1) >= 16 && (octets[1] ?? -1) <= 31 || octets[0] === 192 && octets[1] === 168;
14295
+ }
14296
+ return family === 6 && (host.startsWith("fc") || host.startsWith("fd") || host === "::1");
14297
+ }
14298
+ function mergeSources(current, incoming) {
14299
+ const merged = [...current];
14300
+ for (const source of incoming) {
14301
+ const existing = merged.find((item) => item.id === source.id);
14302
+ if (existing) {
14303
+ Object.assign(existing, source);
14304
+ } else {
14305
+ merged.push(source);
14306
+ }
14307
+ }
14308
+ return merged.slice(0, 12);
14309
+ }
14310
+ function normalizeText(value, maxLength) {
14311
+ return value.trim().replace(/\s+/g, " ").slice(0, maxLength);
14312
+ }
14313
+ function normalizeKey(value) {
14314
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
14315
+ }
14316
+ function clampConfidence(value) {
14317
+ return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0));
14318
+ }
14319
+ function isProductIntelligenceStore(value) {
14320
+ if (!value || typeof value !== "object")
14321
+ return false;
14322
+ const item = value;
14323
+ return item.version === 1 && typeof item.repoName === "string" && typeof item.updatedAt === "string" && Array.isArray(item.facts) && item.facts.every(isFact) && Array.isArray(item.openQuestions) && item.openQuestions.every((question) => typeof question === "string") && Array.isArray(item.decisions) && item.decisions.every((decision) => typeof decision === "string");
14324
+ }
14325
+ function isFact(value) {
14326
+ if (!value || typeof value !== "object")
14327
+ return false;
14328
+ const item = value;
14329
+ return typeof item.id === "string" && typeof item.kind === "string" && typeof item.statement === "string" && Array.isArray(item.appliesTo) && typeof item.status === "string" && typeof item.confidence === "number" && Array.isArray(item.sources) && item.sources.every(isSource) && typeof item.createdAt === "string" && typeof item.updatedAt === "string";
14330
+ }
14331
+ function isSource(value) {
14332
+ if (!value || typeof value !== "object")
14333
+ return false;
14334
+ const item = value;
14335
+ return typeof item.id === "string" && typeof item.kind === "string" && typeof item.label === "string" && typeof item.accessedAt === "string";
14336
+ }
14337
+ var productIntelligenceFile, staleMarketDays, staleProductDays;
14338
+ var init_productIntelligence = __esm({
14339
+ "../core/dist/productIntelligence.js"() {
14340
+ "use strict";
14341
+ init_localCache();
14342
+ init_localLearning();
14343
+ init_utils();
14344
+ productIntelligenceFile = "product-intelligence.json";
14345
+ staleMarketDays = 90;
14346
+ staleProductDays = 180;
14347
+ }
14348
+ });
14349
+
14024
14350
  // ../core/dist/productOperatingContext.js
14025
14351
  function buildProductOperatingContext(scan, request) {
14026
14352
  const files = unique(request.files ?? []);
@@ -14042,6 +14368,16 @@ function buildProductOperatingContext(scan, request) {
14042
14368
  });
14043
14369
  const status = statusFor(taskClasses, reuseBeforeBuild, productRules);
14044
14370
  const score = scoreFor(status, reuseBeforeBuild, productRules, terminology);
14371
+ const productIntelligence = request.repoRoot ? buildProductIntelligenceContext(loadProductIntelligence(request.repoRoot), request.task, Math.max(4, Math.min(12, (request.maxLines ?? 32) - 8))) : {
14372
+ status: "not_available",
14373
+ summary: "Product intelligence is not available without a repo root.",
14374
+ relevantFacts: [],
14375
+ staleFactCount: 0,
14376
+ sourceCount: 0,
14377
+ openQuestions: [],
14378
+ compactText: "Product intelligence: not available without a repo root.",
14379
+ next: "Resolve the repository before reading product context."
14380
+ };
14045
14381
  const summary = status === "not_applicable" ? `${scan.repoName}: no product/design operating context was needed for this task.` : `${scan.repoName}: ${taskClasses.join(", ")} work should reuse ${reuseBeforeBuild.length} existing pattern${reuseBeforeBuild.length === 1 ? "" : "s"} and satisfy ${acceptanceCriteria.length} acceptance checks before handoff.`;
14046
14382
  const compactText = compactTextFor({
14047
14383
  summary,
@@ -14051,6 +14387,7 @@ function buildProductOperatingContext(scan, request) {
14051
14387
  acceptanceCriteria,
14052
14388
  terminology,
14053
14389
  stopSignals: stopSignals2,
14390
+ productIntelligence,
14054
14391
  maxLines: request.maxLines ?? 32
14055
14392
  });
14056
14393
  return {
@@ -14070,9 +14407,10 @@ function buildProductOperatingContext(scan, request) {
14070
14407
  validation,
14071
14408
  productMemoryCandidates,
14072
14409
  stopSignals: stopSignals2,
14410
+ productIntelligence,
14073
14411
  compactText,
14074
14412
  lineCount: compactText.split(/\r?\n/).length,
14075
- next: status === "not_applicable" ? "Continue with the normal before-you-code packet." : "Read compactText before editing; reuse or rule out the listed patterns, then validate the journey, terminology, state, and CTA checks before handoff."
14413
+ next: status === "not_applicable" ? "Continue with the normal before-you-code packet." : `Read compactText before editing; reuse or rule out the listed patterns, then validate the journey, terminology, state, and CTA checks before handoff. ${productIntelligence.next}`
14076
14414
  };
14077
14415
  }
14078
14416
  function needsProductOperatingContext(request) {
@@ -14530,6 +14868,7 @@ function compactTextFor(input) {
14530
14868
  ...input.terminology.preferred.length > 0 ? [
14531
14869
  `Terminology: use ${input.terminology.preferred.slice(0, 8).join(", ")}.`
14532
14870
  ] : [],
14871
+ ...input.productIntelligence.status !== "not_available" ? input.productIntelligence.compactText.split(/\r?\n/) : [],
14533
14872
  "Stop if:",
14534
14873
  ...itemLines(input.stopSignals, (item) => item, "No stop signals.", 4)
14535
14874
  ];
@@ -14558,6 +14897,7 @@ var init_productOperatingContext = __esm({
14558
14897
  "../core/dist/productOperatingContext.js"() {
14559
14898
  "use strict";
14560
14899
  init_utils();
14900
+ init_productIntelligence();
14561
14901
  PRODUCT_KEYWORDS = [
14562
14902
  "app",
14563
14903
  "cta",
@@ -14566,6 +14906,16 @@ var init_productOperatingContext = __esm({
14566
14906
  "feature",
14567
14907
  "flow",
14568
14908
  "journey",
14909
+ "jtbd",
14910
+ "job-to-be-done",
14911
+ "persona",
14912
+ "competitor",
14913
+ "market",
14914
+ "research",
14915
+ "taste",
14916
+ "integration",
14917
+ "customer",
14918
+ "feedback",
14569
14919
  "page",
14570
14920
  "product",
14571
14921
  "screen",
@@ -16803,6 +17153,9 @@ function recipeFor(explicitRecipe, task, request) {
16803
17153
  return missionRecipes.find((recipe) => recipe.id === explicitRecipe) ?? missionRecipes[0];
16804
17154
  }
16805
17155
  const lower = task.toLowerCase();
17156
+ if (/\b(dogfood|dogfooding|exploratory browser|web app smoke|browser smoke|product safari|try the app|use the app)\b/.test(lower) && /\b(app|site|web|browser|page|screen|flow|journey|ui|ux|localhost|staging|production)\b/.test(lower)) {
17157
+ return recipeById("web_dogfood");
17158
+ }
16806
17159
  if (isDesignLedChange(lower)) {
16807
17160
  return recipeById("design_change");
16808
17161
  }
@@ -17153,6 +17506,9 @@ function missionSummary(repoName, recipe, autonomy, request) {
17153
17506
  if (recipe.id === "qa_edge_cases") {
17154
17507
  return `${repoName} can run this as the QA edge-case loop: find missing coverage around risky behavior, add or improve tests, validate the signal, then review the diff.`;
17155
17508
  }
17509
+ if (recipe.id === "web_dogfood") {
17510
+ return `${repoName} can run this as a read-only browser dogfooding loop: name the critical journeys, exercise the app like a user, capture reproducible evidence, separate product friction from environment noise, then hand back the top fixes and reusable learning.`;
17511
+ }
17156
17512
  if (recipe.id === "performance") {
17157
17513
  return `${repoName} can run this as the performance loop: capture a baseline, form a bounded optimization hypothesis, measure before and after, review the diff, then save handoff proof.`;
17158
17514
  }
@@ -17295,6 +17651,15 @@ function missionGates(autonomy, recipe, options2) {
17295
17651
  failureMode: "Do not add low-signal tests or hide flaky checks. Ask for the behavior risk and validation target."
17296
17652
  });
17297
17653
  }
17654
+ if (recipe.id === "web_dogfood") {
17655
+ gates.push({
17656
+ id: "browser_journey",
17657
+ label: "Browser journey gate",
17658
+ requiredTool: "available browser",
17659
+ passCriteria: "The agent names safe starting state, critical journeys, expected outcomes, stop conditions, and evidence fields before interacting with the app.",
17660
+ failureMode: "Do not click through an unclear or destructive flow. Ask for a safe fixture, account, URL, or narrower journey list."
17661
+ });
17662
+ }
17298
17663
  if (recipe.id === "performance") {
17299
17664
  gates.push({
17300
17665
  id: "benchmark_baseline",
@@ -17409,6 +17774,17 @@ function missionSteps(task, autonomy, recipe, options2) {
17409
17774
  gateId: "plan"
17410
17775
  }
17411
17776
  ];
17777
+ if (recipe.id === "web_dogfood") {
17778
+ steps.push({
17779
+ id: "browser_journeys",
17780
+ title: "Exercise critical browser journeys",
17781
+ objective: "Use the app like a real user and gather evidence before proposing a fix.",
17782
+ agentAction: "Start from the named safe state, exercise 2-5 critical journeys, capture expected versus actual behavior, and stop before destructive or credential-requiring actions.",
17783
+ briefTool: "available browser",
17784
+ expectedOutput: "Journey evidence with routes, actions, screenshots or DOM notes, viewport/accessibility checks, and console/network observations.",
17785
+ gateId: "browser_journey"
17786
+ });
17787
+ }
17412
17788
  if (recipe.id === "ticket_write") {
17413
17789
  steps.push({
17414
17790
  id: "capture_judgment",
@@ -17849,6 +18225,17 @@ function selectMissionValidation(suggestedTests, recipe, needsRuntimeEvidence, n
17849
18225
  "Run brief.review_diff before PR handoff."
17850
18226
  ]);
17851
18227
  }
18228
+ if (recipe.id === "web_dogfood") {
18229
+ return unique([
18230
+ "Name 2-5 user journeys with a safe starting state, expected outcome, and stop condition.",
18231
+ "Exercise rendered, user-visible behavior before inferring implementation causes.",
18232
+ "For each finding capture route, reproducible steps, expected vs actual, severity, and screenshot or DOM evidence when useful.",
18233
+ "Check desktop and narrow/mobile layout, keyboard/focus, accessible names, console errors, and failed requests when relevant.",
18234
+ "Separate product friction from auth, fixture, network, or environment limitations.",
18235
+ "End with the top three fixes, what worked, and one reusable learning; do not edit the repo in read-only mode.",
18236
+ "Save the dogfooding report with brief.save_handoff and verify its receipts; use brief.save_learning only for repeatable lessons."
18237
+ ]);
18238
+ }
17852
18239
  if (recipe.id === "performance") {
17853
18240
  return unique([
17854
18241
  "Benchmark baseline names workload, command, metric, variance, and success threshold before edits.",
@@ -17938,25 +18325,31 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
17938
18325
  const isTicketWriter = mode === "ticket_writer";
17939
18326
  const isTicketFix = mode === "ticket_fix";
17940
18327
  const isRuntimeFix = mode === "runtime_fix";
18328
+ const isWebDogfood = recipe.id === "web_dogfood";
17941
18329
  const isSecurityRecipe = recipe.id === "security_dependency" || recipe.id === "security_finding" || recipe.id === "security_testing";
17942
18330
  const handoffTarget = deliveryReviewTarget(deliveryMode);
17943
18331
  const phases = [
17944
18332
  {
17945
- id: isTicketWriter ? "ticket_intake" : "context_map",
17946
- title: isTicketWriter ? "Capture intent and judgment boundaries" : "Load team context and map the repo surface",
18333
+ id: isTicketWriter ? "ticket_intake" : isWebDogfood ? "context_and_surface" : "context_map",
18334
+ title: isTicketWriter ? "Capture intent and judgment boundaries" : isWebDogfood ? "Load app context and safe journey surface" : "Load team context and map the repo surface",
17947
18335
  required: true,
17948
18336
  tool: isTicketWriter ? "brief.write_ticket" : "brief.context_for_task + brief.codebase_map",
17949
- expectedArtifact: isTicketWriter ? "generatedArtifacts.automationTicket" : "context_receipt + map_proof",
18337
+ expectedArtifact: isTicketWriter ? "generatedArtifacts.automationTicket" : isWebDogfood ? "context_receipt + map_proof + browser target" : "context_receipt + map_proof",
17950
18338
  proofRequired: isTicketWriter ? [
17951
18339
  "ticketReadiness.status",
17952
18340
  "automationTicket.machineReadableFields",
17953
18341
  "automationTicket.executionContract"
18342
+ ] : isWebDogfood ? [
18343
+ "contextReceipt.packetId",
18344
+ "executionContract.evidenceContract.mapProof",
18345
+ "context.likelyFiles",
18346
+ "browser target and safe starting state"
17954
18347
  ] : [
17955
18348
  "contextReceipt.packetId",
17956
18349
  "executionContract.evidenceContract.mapProof",
17957
18350
  "context.likelyFiles"
17958
18351
  ],
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."
18352
+ stopIfMissing: isTicketWriter ? "Do not send the ticket until product intent, owner judgment, acceptance criteria, and stop conditions are explicit." : isWebDogfood ? "Do not interact until the app URL, safe starting state, critical journeys, and destructive-action stop conditions are explicit." : "Do not edit until likely files, rules, tests, owners, and affected systems are grounded in Brief context."
17960
18353
  },
17961
18354
  ...isRuntimeFix ? [
17962
18355
  {
@@ -17976,6 +18369,23 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
17976
18369
  stopIfMissing: "Do not code from a runtime symptom until the source, identifier, environment, time window, observed facts, and privacy boundary are present."
17977
18370
  }
17978
18371
  ] : [],
18372
+ ...isWebDogfood ? [
18373
+ {
18374
+ id: "browser_journeys",
18375
+ title: "Exercise critical user journeys",
18376
+ required: true,
18377
+ tool: "available browser",
18378
+ expectedArtifact: "generatedArtifacts.browserJourneyEvidence",
18379
+ proofRequired: [
18380
+ "journey name and starting state",
18381
+ "route and user-visible actions",
18382
+ "expected versus actual outcome",
18383
+ "screenshot or DOM evidence when useful",
18384
+ "viewport, keyboard/focus, console, and failed-request notes"
18385
+ ],
18386
+ stopIfMissing: "Stop before destructive actions, credential entry, or ambiguous state changes; record the environment limitation instead."
18387
+ }
18388
+ ] : [],
17979
18389
  {
17980
18390
  id: "plan_loop",
17981
18391
  title: "Convert the work into a bounded loop",
@@ -17992,53 +18402,70 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
17992
18402
  },
17993
18403
  ...isTicketWriter ? [] : [
17994
18404
  {
17995
- id: "execute_change",
17996
- title: "Make the smallest bounded change",
17997
- required: autonomy !== "read_only",
17998
- tool: "coding agent",
17999
- expectedArtifact: "local diff or no-code-change explanation",
18000
- proofRequired: [
18405
+ id: isWebDogfood ? "read_only_pass" : "execute_change",
18406
+ title: isWebDogfood ? "Keep the pass read-only" : "Make the smallest bounded change",
18407
+ required: isWebDogfood ? true : autonomy !== "read_only",
18408
+ tool: isWebDogfood ? "available browser" : "coding agent",
18409
+ expectedArtifact: isWebDogfood ? "journey evidence or explicit environment gap" : "local diff or no-code-change explanation",
18410
+ proofRequired: isWebDogfood ? [
18411
+ "no repo files changed",
18412
+ "journey evidence is reproducible",
18413
+ "product friction is separated from environment noise"
18414
+ ] : [
18001
18415
  "changedFiles[]",
18002
18416
  "rootCause or explicit no-root-cause finding",
18003
18417
  "acceptanceCriteriaStatus[]"
18004
18418
  ],
18005
- stopIfMissing: "Stop when the likely fix exceeds the ticket, runtime evidence, or selected autonomy boundary."
18419
+ stopIfMissing: isWebDogfood ? "Stop when a journey needs an external side effect, private credential, or unsupported browser capability." : "Stop when the likely fix exceeds the ticket, runtime evidence, or selected autonomy boundary."
18006
18420
  },
18007
18421
  {
18008
- id: "validate_change",
18009
- title: "Run the validation loop",
18422
+ id: isWebDogfood ? "review_evidence" : "validate_change",
18423
+ title: isWebDogfood ? "Review and classify the evidence" : "Run the validation loop",
18010
18424
  required: true,
18011
- tool: "brief.find_tests + project commands",
18012
- expectedArtifact: "validation evidence",
18013
- proofRequired: validation.length ? validation.slice(0, 5) : [
18425
+ tool: isWebDogfood ? "browser evidence + agent judgment" : "brief.find_tests + project commands",
18426
+ expectedArtifact: isWebDogfood ? "classified dogfooding findings" : "validation evidence",
18427
+ proofRequired: isWebDogfood ? [
18428
+ "finding severity and reproducible steps",
18429
+ "bug versus UX friction versus environment classification",
18430
+ "top fixes and what worked"
18431
+ ] : validation.length ? validation.slice(0, 5) : [
18014
18432
  "selected validation command",
18015
18433
  "pass/fail/skipped result",
18016
18434
  "environment note"
18017
18435
  ],
18018
- stopIfMissing: `Do not ask for ${handoffTarget} until validation is run or the missing environment is named with an owner.`
18436
+ stopIfMissing: isWebDogfood ? "Do not report a product issue without a route, action, expected result, actual result, and evidence or an explicit environment limitation." : `Do not ask for ${handoffTarget} until validation is run or the missing environment is named with an owner.`
18019
18437
  },
18020
18438
  {
18021
- id: "challenge_review",
18022
- title: "Challenge the diff and the root cause",
18439
+ id: isWebDogfood ? "prioritize_findings" : "challenge_review",
18440
+ title: isWebDogfood ? "Prioritize the user impact" : "Challenge the diff and the root cause",
18023
18441
  required: true,
18024
- tool: "brief.review_diff",
18025
- expectedArtifact: "review receipt",
18026
- proofRequired: [
18442
+ tool: isWebDogfood ? "brief.save_learning (when repeatable)" : "brief.review_diff",
18443
+ expectedArtifact: isWebDogfood ? "prioritized product feedback" : "review receipt",
18444
+ proofRequired: isWebDogfood ? [
18445
+ "top three fixes by user impact and reproducibility",
18446
+ "one thing that worked",
18447
+ "one reusable learning or explicit no-learning decision"
18448
+ ] : [
18027
18449
  "review.verdict",
18028
18450
  "review.readiness.status",
18029
18451
  "review receipt id",
18030
18452
  "blocker/high findings resolved or explicitly accepted"
18031
18453
  ],
18032
- stopIfMissing: "Do not hand off when blocker or high review findings remain unresolved."
18454
+ stopIfMissing: isWebDogfood ? "Do not hand off a long undifferentiated bug list; rank the few findings that most affect a real user." : "Do not hand off when blocker or high review findings remain unresolved."
18033
18455
  }
18034
18456
  ],
18035
18457
  {
18036
18458
  id: "handoff_receipts",
18037
- title: isTicketWriter ? "Save the ticket contract and receipt ledger" : "Save handoff proof and verify receipts",
18459
+ title: isTicketWriter ? "Save the ticket contract and receipt ledger" : isWebDogfood ? "Save the dogfooding report and verify receipts" : "Save handoff proof and verify receipts",
18038
18460
  required: true,
18039
18461
  tool: "brief.save_handoff + brief.verify_receipts",
18040
- expectedArtifact: "proof bundle + receipt ledger verification",
18041
- proofRequired: [
18462
+ expectedArtifact: isWebDogfood ? "dogfooding report + receipt ledger verification" : "proof bundle + receipt ledger verification",
18463
+ proofRequired: isWebDogfood ? [
18464
+ "proofBundle.status",
18465
+ "proofBundle.receipts.handoffId",
18466
+ "proofBundle.missingEvidence",
18467
+ "receiptLedger.status"
18468
+ ] : [
18042
18469
  "proofBundle.status",
18043
18470
  "proofBundle.receipts.handoffId",
18044
18471
  "proofBundle.receipts.reviewReceiptId",
@@ -18072,7 +18499,7 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
18072
18499
  "MCP-capable IDE",
18073
18500
  "console loop recipe"
18074
18501
  ],
18075
- evidenceSources: automationLoopEvidenceSources(mode, options2),
18502
+ evidenceSources: automationLoopEvidenceSources(mode, options2, recipe.id),
18076
18503
  dataBoundaries: [
18077
18504
  "Use derived or redacted runtime facts in prompts; do not paste raw customer payloads, secrets, tokens, or private logs.",
18078
18505
  "Keep observed facts, hypotheses, and human decisions separate in the ticket or handoff.",
@@ -18089,7 +18516,10 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
18089
18516
  "Root cause is inferred from symptoms rather than proven by source, CI, logs, traces, or reproduction.",
18090
18517
  "Two plausible approaches compete or the change touches architecture, data, auth, migrations, billing, or customer-visible behavior.",
18091
18518
  "The agent changes files outside the mapped scope or selected autonomy.",
18092
- "brief.review_diff returns blocker or high findings.",
18519
+ ...isWebDogfood ? [] : ["brief.review_diff returns blocker or high findings."],
18520
+ ...isWebDogfood ? [
18521
+ "A journey result is ambiguous, not reproducible, or could be explained by auth, fixture, network, or deployment state."
18522
+ ] : [],
18093
18523
  ...isRuntimeFix ? [
18094
18524
  "Runtime evidence is stale, contradictory, privacy-sensitive, or cannot be tied to the changed code path."
18095
18525
  ] : [],
@@ -18119,7 +18549,7 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
18119
18549
  ] : []
18120
18550
  ]),
18121
18551
  finalUpdate: {
18122
- destination: deliveryFinalDestination(deliveryMode, isTicketWriter ? "Linear, Jira, or GitHub issue body" : isRuntimeFix ? "source ticket, incident, alert thread, or PR handoff" : isTicketFix ? "source Linear, Jira, GitHub, or pasted ticket" : isSecurityRecipe ? "security alert, Dependabot item, scanner finding, PR description, or handoff notes" : "PR description or handoff notes"),
18552
+ destination: deliveryFinalDestination(deliveryMode, isTicketWriter ? "Linear, Jira, or GitHub issue body" : isWebDogfood ? "product feedback, bug triage, or follow-up mission" : isRuntimeFix ? "source ticket, incident, alert thread, or PR handoff" : isTicketFix ? "source Linear, Jira, GitHub, or pasted ticket" : isSecurityRecipe ? "security alert, Dependabot item, scanner finding, PR description, or handoff notes" : "PR description or handoff notes"),
18123
18553
  requiredSections: isTicketWriter ? [
18124
18554
  "Intent",
18125
18555
  "Automation Readiness",
@@ -18128,26 +18558,42 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
18128
18558
  "Brief Evidence Contract",
18129
18559
  "Acceptance Criteria",
18130
18560
  "Stop Conditions"
18561
+ ] : isWebDogfood ? [
18562
+ "Journeys Tested",
18563
+ "What Worked",
18564
+ "Findings",
18565
+ "Environment Limits",
18566
+ "Top Fixes",
18567
+ "Reusable Learning"
18131
18568
  ] : ticketIssueUpdateSections(),
18132
18569
  proofFields: [
18133
18570
  "proofBundle.status",
18134
18571
  "proofBundle.receipts.handoffId",
18135
18572
  "proofBundle.receipts.contextReceiptId",
18136
- "proofBundle.receipts.reviewReceiptId",
18137
- "proofBundle.receipts.reviewVerdict",
18573
+ ...isWebDogfood ? [] : [
18574
+ "proofBundle.receipts.reviewReceiptId",
18575
+ "proofBundle.receipts.reviewVerdict"
18576
+ ],
18138
18577
  "proofBundle.validation.passed",
18139
18578
  "proofBundle.missingEvidence"
18140
18579
  ],
18141
18580
  postOnlyWhen: [
18142
- "brief.review_diff has no unresolved blocker/high findings.",
18581
+ ...isWebDogfood ? [
18582
+ "Each reported finding has reproducible steps and evidence or an explicit environment limitation."
18583
+ ] : ["brief.review_diff has no unresolved blocker/high findings."],
18143
18584
  "brief.verify_receipts is trusted or context_only with gaps explained.",
18144
- "Acceptance criteria have pass/fail/skipped evidence.",
18145
- "Remaining risk and owner are named."
18585
+ ...isWebDogfood ? ["The top user-impact fixes and what worked are named."] : [
18586
+ "Acceptance criteria have pass/fail/skipped evidence.",
18587
+ "Remaining risk and owner are named."
18588
+ ]
18146
18589
  ],
18147
18590
  doNotPostWhen: unique([
18148
18591
  "Required owner, product, security, data, or rollout judgment is missing.",
18149
18592
  "Validation is missing and no human owner accepted the gap.",
18150
18593
  "The change exceeded ticket scope, runtime evidence scope, or autonomy.",
18594
+ ...isWebDogfood ? [
18595
+ "A destructive action, private credential, or external side effect was required without approval."
18596
+ ] : [],
18151
18597
  ...isRuntimeFix ? [
18152
18598
  "Runtime evidence packet is missing, stale, contradictory, or contains unresolved privacy gaps."
18153
18599
  ] : [],
@@ -18168,8 +18614,15 @@ function automationLoopMode(recipe, options2) {
18168
18614
  return "ticket_fix";
18169
18615
  return "mission";
18170
18616
  }
18171
- function automationLoopEvidenceSources(mode, options2) {
18617
+ function automationLoopEvidenceSources(mode, options2, recipeId) {
18172
18618
  return unique([
18619
+ ...recipeId === "web_dogfood" ? [
18620
+ "available browser",
18621
+ "journey steps and expected outcomes",
18622
+ "screenshots or DOM snapshots",
18623
+ "console and network observations",
18624
+ "responsive and accessibility checks"
18625
+ ] : [],
18173
18626
  ...mode === "ticket_writer" ? [
18174
18627
  "ticket intent",
18175
18628
  "acceptance criteria",
@@ -18205,6 +18658,7 @@ function loopProofContract(recipe, options2, deliveryMode) {
18205
18658
  const isTicketWriter = mode === "ticket_writer";
18206
18659
  const needsRuntimeEvidence = mode === "runtime_fix";
18207
18660
  const needsWorkItem = mode === "ticket_fix" || options2.needsWorkItem;
18661
+ const isWebDogfood = recipe.id === "web_dogfood";
18208
18662
  const isSecurityDependency = recipe.id === "security_dependency";
18209
18663
  const isSecurityFinding = recipe.id === "security_finding";
18210
18664
  const isSecurityTesting = recipe.id === "security_testing";
@@ -18214,7 +18668,12 @@ function loopProofContract(recipe, options2, deliveryMode) {
18214
18668
  "map_proof",
18215
18669
  "team_rules_applied",
18216
18670
  "mission_receipt",
18217
- ...isTicketWriter ? ["ticket_readiness", "human_judgment_record", "automation_ticket"] : [
18671
+ ...isTicketWriter ? ["ticket_readiness", "human_judgment_record", "automation_ticket"] : isWebDogfood ? [
18672
+ "browser_journey_plan",
18673
+ "browser_journey_evidence",
18674
+ "dogfooding_report",
18675
+ "handoff_receipt"
18676
+ ] : [
18218
18677
  "acceptance_criteria_evidence",
18219
18678
  "validation_evidence",
18220
18679
  "review_readiness",
@@ -18243,7 +18702,8 @@ function loopProofContract(recipe, options2, deliveryMode) {
18243
18702
  ]),
18244
18703
  requiredReceipts: unique([
18245
18704
  "mission_plan",
18246
- ...isTicketWriter ? [] : ["review", "handoff"],
18705
+ ...isTicketWriter || isWebDogfood ? [] : ["review", "handoff"],
18706
+ ...isWebDogfood ? ["handoff"] : [],
18247
18707
  "receipt_ledger"
18248
18708
  ]),
18249
18709
  readyStatuses: {
@@ -18257,6 +18717,11 @@ function loopProofContract(recipe, options2, deliveryMode) {
18257
18717
  "Human judgment, acceptance criteria, non-goals, and owner boundaries are explicit.",
18258
18718
  "Machine-readable run fields and Brief evidence contract are present.",
18259
18719
  "Receipt ledger verifies the mission receipt digest."
18720
+ ] : isWebDogfood ? [
18721
+ "Browser journeys are named, exercised from a safe state, and reported with reproducible evidence.",
18722
+ "Product issues are separated from auth, fixture, network, browser-capability, and deployment limitations.",
18723
+ "The report is concise: top user-impact fixes, what worked, and reusable learning or an explicit no-learning decision.",
18724
+ "A handoff receipt and receipt-ledger verification are present; review_diff is optional because no repo diff is expected."
18260
18725
  ] : [
18261
18726
  "Runtime evidence packet is ready when runtime evidence is part of the mission.",
18262
18727
  ...isSecurityDependency ? [
@@ -18343,6 +18808,11 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
18343
18808
  "generatedArtifacts.contentProofPack.readiness",
18344
18809
  "generatedArtifacts.productOperatingContext"
18345
18810
  ] : [],
18811
+ ...recipe.id === "web_dogfood" ? [
18812
+ "generatedArtifacts.browserJourneyEvidence",
18813
+ "generatedArtifacts.dogfoodingReport",
18814
+ "generatedArtifacts.dogfoodingReport.topFindings[]"
18815
+ ] : [],
18346
18816
  "runContract.exitCodes[]"
18347
18817
  ],
18348
18818
  artifacts,
@@ -18351,8 +18821,8 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
18351
18821
  proofContract,
18352
18822
  successCriteria: [
18353
18823
  "The context receipt cites the rules, source paths, tests, owners, and memories used by the agent.",
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.",
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.",
18824
+ 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 === "web_dogfood" ? "The dogfooding packet names critical journeys, safe starting state, expected outcomes, stop conditions, and evidence policy." : 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.",
18825
+ 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 === "web_dogfood" ? "The browser report contains reproducible journey evidence, separates product friction from environment noise, and ranks the top user-impact fixes." : 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.",
18356
18826
  autonomy === "read_only" ? "No repo files are changed." : "The local diff has no blocker or high Brief findings before human review.",
18357
18827
  "Useful repeated feedback is saved or intentionally skipped with a reason."
18358
18828
  ],
@@ -18400,6 +18870,12 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
18400
18870
  "The proposed tests do not map to observable behavior risk or only assert implementation details.",
18401
18871
  "The change hides failures by muting, skipping, or weakening validation."
18402
18872
  ] : [],
18873
+ ...recipe.id === "web_dogfood" ? [
18874
+ "The app URL, safe starting state, critical journey, or expected outcome is missing.",
18875
+ "A journey requires destructive action, private credentials, production data, or an external side effect without explicit approval.",
18876
+ "A finding is reported without reproducible steps, route, expected versus actual behavior, and evidence or an explicit environment limitation.",
18877
+ "Browser, auth, fixture, network, or deployment noise is presented as a product defect without confirmation."
18878
+ ] : [],
18403
18879
  ...recipe.id === "performance" ? [
18404
18880
  "The benchmark, workload, baseline, variance, or success threshold is missing.",
18405
18881
  "The optimization makes readability, correctness, or operability worse without an explicit human tradeoff."
@@ -18552,6 +19028,30 @@ function missionArtifacts(recipe, autonomy, options2) {
18552
19028
  description: "Labels, helper text, descriptions, errors, announcements, and screen-reader context remain accurate after copy changes."
18553
19029
  });
18554
19030
  }
19031
+ if (recipe.id === "web_dogfood") {
19032
+ artifacts.push({
19033
+ id: "browser_journey_plan",
19034
+ label: "Browser journey plan",
19035
+ kind: "plan",
19036
+ producer: "brief.mission_plan",
19037
+ required: true,
19038
+ description: "Safe starting state, critical user journeys, expected outcomes, stop conditions, viewport plan, and evidence policy."
19039
+ }, {
19040
+ id: "browser_journey_evidence",
19041
+ label: "Browser journey evidence",
19042
+ kind: "validation",
19043
+ producer: "available browser",
19044
+ required: true,
19045
+ description: "Journey-by-journey route, actions, expected versus actual behavior, screenshot or DOM evidence, viewport, accessibility notes, and console/network result."
19046
+ }, {
19047
+ id: "dogfooding_report",
19048
+ label: "Dogfooding report",
19049
+ kind: "handoff",
19050
+ producer: "brief.save_handoff",
19051
+ required: true,
19052
+ description: "Concise findings ranked by user impact and reproducibility, environment limitations, top fixes, what worked, and reusable learning."
19053
+ });
19054
+ }
18555
19055
  if (recipe.id === "internal_app") {
18556
19056
  artifacts.push({
18557
19057
  id: "product_operating_context",
@@ -19020,6 +19520,30 @@ function missionHookEvents(recipe, autonomy, options2) {
19020
19520
  mode: "advisory"
19021
19521
  });
19022
19522
  }
19523
+ if (recipe.id === "web_dogfood") {
19524
+ events.push({
19525
+ id: "before_browser_journey",
19526
+ label: "Before browser journey",
19527
+ timing: "before",
19528
+ trigger: "An agent starts an exploratory pass through a local, staging, or deployed web app.",
19529
+ action: "Require safe starting state, 2-5 critical journeys, expected outcomes, viewport plan, evidence fields, and destructive-action stop conditions.",
19530
+ mode: "blocking"
19531
+ }, {
19532
+ id: "after_browser_journey",
19533
+ label: "After browser journey",
19534
+ timing: "after",
19535
+ trigger: "A browser journey or exploratory scenario completes.",
19536
+ action: "Classify findings as product bug, UX friction, accessibility issue, or environment limitation; attach reproducible evidence and rank the top user-impact fixes.",
19537
+ mode: "blocking"
19538
+ }, {
19539
+ id: "before_dogfood_handoff",
19540
+ label: "Before dogfooding handoff",
19541
+ timing: "before",
19542
+ trigger: "The agent prepares a browser dogfooding report.",
19543
+ action: "Save the concise report and receipt ledger; skip review_diff when no repo files changed, and save only repeatable lessons to shared learning.",
19544
+ mode: "blocking"
19545
+ });
19546
+ }
19023
19547
  if (recipe.id === "ticket_write") {
19024
19548
  events.push({
19025
19549
  id: "before_ticket_execution",
@@ -19651,6 +20175,31 @@ var init_missionPlan = __esm({
19651
20175
  "learn"
19652
20176
  ]
19653
20177
  },
20178
+ {
20179
+ id: "web_dogfood",
20180
+ title: "Web app dogfooding loop",
20181
+ whenToUse: "Exercise a local or deployed web app like a real user, find the highest-friction moments, and return evidence-backed product feedback before changing code.",
20182
+ defaultAutonomy: "read_only",
20183
+ primaryOutput: "Journey-based dogfooding report with reproducible findings, screenshots, DOM/console/network evidence, accessibility and responsive checks, and prioritized next actions.",
20184
+ requiredGates: [
20185
+ "brief.context_for_task",
20186
+ "brief.codebase_map",
20187
+ "brief.mission_plan",
20188
+ "browser journey evidence",
20189
+ "brief.save_handoff",
20190
+ "brief.verify_receipts",
20191
+ "brief.save_learning"
20192
+ ],
20193
+ loopIds: [
20194
+ "research",
20195
+ "journey",
20196
+ "browser",
20197
+ "validate",
20198
+ "review",
20199
+ "handoff",
20200
+ "learn"
20201
+ ]
20202
+ },
19654
20203
  {
19655
20204
  id: "performance",
19656
20205
  title: "Performance loop",
@@ -20285,6 +20834,51 @@ var init_missionPlan = __esm({
20285
20834
  "Signal quality improved rather than only coverage count."
20286
20835
  ]
20287
20836
  }),
20837
+ web_dogfood: workPattern({
20838
+ title: "Web app dogfooding pattern",
20839
+ teamValue: "Turns browser exploration into a compact, source-grounded product report: real user journeys, visual and interaction evidence, prioritized friction, and a decision about what to fix next.",
20840
+ goodFirstRun: 'brief.mission_plan({ recipe: "web_dogfood", task: "Dogfood <app URL> through <critical journeys>", autonomy: "read_only" })',
20841
+ recommendedSurfaces: [
20842
+ "Codex in-app browser",
20843
+ "Playwright",
20844
+ "Claude browser tools",
20845
+ "Browser MCP",
20846
+ "Storybook or app preview"
20847
+ ],
20848
+ dataContext: [
20849
+ "critical user journeys",
20850
+ "app URL and environment",
20851
+ "repo code map and route ownership",
20852
+ "design system and product rules",
20853
+ "browser screenshots and DOM snapshots",
20854
+ "console errors and failed network requests",
20855
+ "responsive and accessibility observations",
20856
+ "prior dogfooding feedback and known issues"
20857
+ ],
20858
+ validationBurden: [
20859
+ "Name the journeys, starting state, test account or safe fixture, and stop conditions before interacting.",
20860
+ "Use visible user behavior and stable labels; do not infer success from a click alone.",
20861
+ "Capture before/after or step evidence for each finding: route, action, expected result, actual result, severity, and reproduction.",
20862
+ "Check the critical path at desktop and narrow/mobile widths, including loading, empty, error, success, keyboard/focus, and long-content states when relevant.",
20863
+ "Inspect console errors and failed requests after a broken or suspicious interaction; distinguish product bugs from environment/auth failures.",
20864
+ "Keep the session read-only unless the user explicitly asks for a fix; a follow-up fix loop must cite this report.",
20865
+ "End with the top three fixes, what worked, and one proposed learning for the team."
20866
+ ],
20867
+ humanReviewFocus: [
20868
+ "The journey represents a real user job rather than a tour of every page.",
20869
+ "Findings are reproducible, prioritized by user impact, and backed by visible evidence.",
20870
+ "A confusing flow is separated from a purely visual preference or test-environment limitation.",
20871
+ "The report recommends the smallest next product change and names what should remain unchanged."
20872
+ ],
20873
+ parallelism: {
20874
+ fit: "lead_agent",
20875
+ guidance: [
20876
+ "Keep one lead agent responsible for the journey map, evidence quality, and final prioritization.",
20877
+ "Use a second browser session only for an independent mobile, accessibility, or fresh-user pass.",
20878
+ "Do not split one journey across agents; a single owner should observe the full interaction chain."
20879
+ ]
20880
+ }
20881
+ }),
20288
20882
  performance: workPattern({
20289
20883
  title: "Performance work pattern",
20290
20884
  teamValue: "Requires a baseline, workload, threshold, measured diff, and rollback signal before optimization work expands.",
@@ -20595,7 +21189,7 @@ function categoryForRecipe(recipeId) {
20595
21189
  if (recipeId === "incident_fix" || recipeId === "bug_investigation" || recipeId === "flaky_test") {
20596
21190
  return "runtime";
20597
21191
  }
20598
- if (recipeId === "feature" || recipeId === "design_change" || recipeId === "content_change" || recipeId === "qa_edge_cases" || recipeId === "small_refactor" || recipeId === "bugfix") {
21192
+ if (recipeId === "feature" || recipeId === "design_change" || recipeId === "content_change" || recipeId === "web_dogfood" || recipeId === "qa_edge_cases" || recipeId === "small_refactor" || recipeId === "bugfix") {
20599
21193
  return "quality";
20600
21194
  }
20601
21195
  if (recipeId === "internal_app")
@@ -20616,6 +21210,8 @@ function toolForRecipe(recipeId) {
20616
21210
  return "brief.write_ticket";
20617
21211
  if (recipeId === "ticket_fix")
20618
21212
  return "brief.fix_ticket";
21213
+ if (recipeId === "web_dogfood")
21214
+ return "browser + brief.save_handoff";
20619
21215
  if (recipeId === "incident_fix" || recipeId === "flaky_test") {
20620
21216
  return "brief.runtime_fix";
20621
21217
  }
@@ -20706,6 +21302,17 @@ function requiredInputsForRecipe(recipeId) {
20706
21302
  "privacy boundary for source, logs, secrets, and customer data"
20707
21303
  ];
20708
21304
  }
21305
+ if (recipeId === "web_dogfood") {
21306
+ return [
21307
+ "app URL, environment, and safe account or fixture",
21308
+ "two to five critical user journeys",
21309
+ "expected outcome and stop condition for each journey",
21310
+ "desktop and mobile or narrow viewport plan",
21311
+ "accessibility checks for changed interactions",
21312
+ "safe screenshot, DOM, console, and network evidence policy",
21313
+ "known feedback or product principles to test"
21314
+ ];
21315
+ }
20709
21316
  if (recipeId === "incident_fix" || recipeId === "flaky_test" || recipeId === "bug_investigation") {
20710
21317
  return [
20711
21318
  "observed failure source",
@@ -20838,6 +21445,17 @@ function proofBarForRecipe(recipeId) {
20838
21445
  "the new signal is saved as team context for future missions"
20839
21446
  ];
20840
21447
  }
21448
+ if (recipeId === "web_dogfood") {
21449
+ return [
21450
+ "journeys are named before exploration",
21451
+ "each finding has route, steps, expected result, actual result, severity, and evidence",
21452
+ "user-visible behavior is checked before implementation details",
21453
+ "console and failed-request evidence is captured for suspicious states",
21454
+ "desktop/narrow and keyboard/focus checks cover the critical path",
21455
+ "environment/auth failures are separated from product defects",
21456
+ "top fixes, what worked, and a team-learning candidate are prioritized"
21457
+ ];
21458
+ }
20841
21459
  if (recipeId === "incident_fix" || recipeId === "bug_investigation") {
20842
21460
  return [
20843
21461
  "observed facts",
@@ -20950,6 +21568,14 @@ function privacyBoundaryForRecipe(recipeId) {
20950
21568
  "keep scanner findings and test fixtures free of real credentials and production payloads"
20951
21569
  ];
20952
21570
  }
21571
+ if (recipeId === "web_dogfood") {
21572
+ return [
21573
+ "use safe fixtures and redact customer data, tokens, cookies, and private URLs",
21574
+ "do not upload screenshots, DOM, console, or network payloads unless the workspace allows it",
21575
+ "prefer derived evidence such as route, action, status, error class, and screenshot reference",
21576
+ "never submit external side effects, purchases, destructive actions, or production writes without explicit approval"
21577
+ ];
21578
+ }
20953
21579
  if (recipeId === "incident_fix" || recipeId === "flaky_test" || recipeId === "bug_investigation") {
20954
21580
  return [
20955
21581
  "summarize raw logs into observed facts first",
@@ -21001,6 +21627,9 @@ function teamOutcomeForRecipe(recipeId) {
21001
21627
  if (recipeId === "security_testing") {
21002
21628
  return "Teams can add security tests and scanning gates that are useful enough to trust and calibrated enough not to slow every developer down.";
21003
21629
  }
21630
+ if (recipeId === "web_dogfood") {
21631
+ return "Teams get a repeatable user-centered browser pass that finds confusing flows, broken states, accessibility gaps, and product friction before customers do, with evidence a coding agent can act on.";
21632
+ }
21004
21633
  if (recipeId === "flaky_test") {
21005
21634
  return "Teams reduce CI noise without normalizing muted, skipped, or blindly retried failures.";
21006
21635
  }
@@ -21041,6 +21670,9 @@ function firstRunPrompts(items) {
21041
21670
  if (item.recipeId === "security_testing") {
21042
21671
  return 'Call brief.mission_plan with recipe "security_testing" for the security test, scanner, dependency-review, or CI gate you want to add.';
21043
21672
  }
21673
+ if (item.recipeId === "web_dogfood") {
21674
+ return 'Call brief.mission_plan with recipe "web_dogfood" for: <app URL and critical user journeys>.';
21675
+ }
21044
21676
  return `Call brief.mission_plan with recipe "${item.recipeId}" for: <describe the work>.`;
21045
21677
  });
21046
21678
  }
@@ -21098,7 +21730,7 @@ function querySynonyms(term) {
21098
21730
  return ["mcp", "client", "surface", "terminal", "ide"];
21099
21731
  }
21100
21732
  if (term === "dogfood" || term === "dogfooding") {
21101
- return ["first", "receipt", "review"];
21733
+ return ["web", "browser", "journey", "receipt", "review"];
21102
21734
  }
21103
21735
  if (term === "codex")
21104
21736
  return ["codex", "terminal", "mcp"];
@@ -21142,6 +21774,9 @@ function queryScore(item, terms) {
21142
21774
  if (termSet.has("quality") && (item.category === "quality" || item.recipeId === "pr_review")) {
21143
21775
  score += 4;
21144
21776
  }
21777
+ if ((termSet.has("quality") || termSet.has("review")) && item.recipeId === "pr_review") {
21778
+ score += 8;
21779
+ }
21145
21780
  if (termSet.has("product") && item.recipeId === "feature") {
21146
21781
  score += 4;
21147
21782
  }
@@ -21160,8 +21795,8 @@ function queryScore(item, terms) {
21160
21795
  if ((termSet.has("scanner") || termSet.has("scanning") || termSet.has("gate") || termSet.has("tests")) && item.recipeId === "security_testing") {
21161
21796
  score += 7;
21162
21797
  }
21163
- if (termSet.has("dogfood") && ["pr_review", "session_library", "small_refactor"].includes(item.recipeId)) {
21164
- score += 2;
21798
+ if (termSet.has("dogfood") && item.recipeId === "web_dogfood") {
21799
+ score += 12;
21165
21800
  }
21166
21801
  if (termSet.has("tdd") && ["feature", "bugfix", "flaky_test", "dependency"].includes(item.recipeId)) {
21167
21802
  score += 4;
@@ -21233,6 +21868,7 @@ var init_agentLoopCatalog = __esm({
21233
21868
  "security_testing",
21234
21869
  "flaky_test",
21235
21870
  "bug_investigation",
21871
+ "web_dogfood",
21236
21872
  "stale_pr",
21237
21873
  "internal_app",
21238
21874
  "pr_review",
@@ -24402,6 +25038,8 @@ function runCardTitle(recipeId) {
24402
25038
  return "Repeat bug investigation loop";
24403
25039
  case "qa_edge_cases":
24404
25040
  return "Repeat QA edge-case loop";
25041
+ case "web_dogfood":
25042
+ return "Repeat web app dogfooding loop";
24405
25043
  case "performance":
24406
25044
  return "Repeat performance loop";
24407
25045
  case "porting":
@@ -24450,6 +25088,8 @@ function runCardUseWhen(recipeId) {
24450
25088
  return "Use when a hard, rare, intermittent, or previously failed bug needs hypotheses, evidence, validation, and a watch signal.";
24451
25089
  case "qa_edge_cases":
24452
25090
  return "Use when important code paths need missing edge cases, fuzz/stress coverage, or stronger behavior tests.";
25091
+ case "web_dogfood":
25092
+ return "Use when a real user journey through a local or deployed web app needs evidence-backed product feedback.";
24453
25093
  case "performance":
24454
25094
  return "Use when latency, throughput, memory, CPU, or benchmark work needs a baseline before code changes.";
24455
25095
  case "porting":
@@ -24565,6 +25205,16 @@ function runCardSteps(recipeId) {
24565
25205
  "decision"
24566
25206
  ];
24567
25207
  }
25208
+ if (recipeId === "web_dogfood") {
25209
+ return [
25210
+ "journeys",
25211
+ "starting state",
25212
+ "browser pass",
25213
+ "evidence",
25214
+ "prioritized findings",
25215
+ "decision"
25216
+ ];
25217
+ }
24568
25218
  if (recipeId === "session_review") {
24569
25219
  return [
24570
25220
  "receipts",
@@ -24683,6 +25333,9 @@ function runCardStopConditions(recipeId) {
24683
25333
  if (recipeId === "experiment") {
24684
25334
  stops.unshift("Success signal, throwaway boundary, discard path, or decision owner is missing.");
24685
25335
  }
25336
+ if (recipeId === "web_dogfood") {
25337
+ stops.unshift("The journey, starting state, expected outcome, or evidence policy is missing.", "The agent is treating a click, route change, or visual preference as proof of success without checking user-visible outcome.", "A screenshot, DOM snapshot, console error, network failure, or auth/environment limitation is being shared without redaction or classification.");
25338
+ }
24686
25339
  if (recipeId === "session_review") {
24687
25340
  return [
24688
25341
  "Raw prompt transcripts, secrets, customer data, or private logs are required without explicit approval.",
@@ -24776,6 +25429,9 @@ function inferLoopId2(task) {
24776
25429
  if (/\b(edge cases?|missing tests?|test gaps?|coverage gaps?|property[- ]based|qa|quality pass|corner cases?)\b/.test(normalized)) {
24777
25430
  return "qa_edge_cases";
24778
25431
  }
25432
+ if (/\b(dogfood|dogfooding|exploratory browser|web app smoke|browser smoke|product safari|try the app|use the app)\b/.test(normalized) && /\b(app|site|web|browser|page|screen|flow|journey|ui|ux|localhost|staging|production)\b/.test(normalized)) {
25433
+ return "web_dogfood";
25434
+ }
24779
25435
  if (/\b(performance|latency|throughput|benchmark|slow|speed|optimi[sz]e|profile|memory|cpu)\b/.test(normalized)) {
24780
25436
  return "performance";
24781
25437
  }
@@ -25443,6 +26099,7 @@ function autoEngageInstruction(scan, skillLearning) {
25443
26099
  "- Read `brief.next.beforeYouCode.compactText` or `brief.start.beforeYouCode.compactText` before the first edit. Cite its target repo, branch, inspect-first files, reuse decision, likely tests, and stop signals in your working notes.",
25444
26100
  "- For micro or small cockpit passes, keep Brief lightweight: inspect, reuse or rule out the top candidate, validate narrowly, and skip mission_plan/save_handoff/verify_receipts unless scope grows.",
25445
26101
  "- For product, design, content, workflow, compliance, internal-app, or wiki work, read `beforeYouCode.productOperatingContext.compactText` or call `brief.product_context` before editing. Reuse or rule out listed patterns, then preserve its journey, terminology, CTA, state-machine, and acceptance checks.",
26102
+ "- When the task asks about users, JTBD, competitors, market alternatives, product taste, microcopy, integrations, or current external behavior, call `brief.product_research_plan` automatically. Use the host's approved web/browser tools for current public evidence, then call `brief.save_product_context` with cited proposed facts; never turn search snippets or competitor claims into defaults without review.",
25446
26103
  "- If the user gives a Linear, Jira, GitHub, Sentry, Datadog, CI, log, trace, or pasted issue reference, pass that exact reference into `brief.start` instead of inventing a separate plan.",
25447
26104
  "- Before creating a new helper, component, route, workflow, template, design pattern, command, or skill, call `brief.reference_implementations` and `brief.codebase_map` to look for reuse first.",
25448
26105
  "- When scope, files, risk, or validation changes mid-task, refresh with `brief.context_for_task`, `brief.rules_for_files`, `brief.codebase_map`, `brief.find_tests`, or `brief.review_memory` before continuing.",
@@ -25526,6 +26183,14 @@ function autoEngageTriggers() {
25526
26183
  agentBehavior: "Look for existing primitives and saved references before adding another implementation.",
25527
26184
  proof: "The final update names reused candidates or explains why new code was justified."
25528
26185
  },
26186
+ {
26187
+ id: "product_research",
26188
+ label: "Product research",
26189
+ when: "The task asks about users, JTBD, competitors, market alternatives, product taste, microcopy, integrations, or current external behavior.",
26190
+ briefCalls: ["brief.product_research_plan", "brief.save_product_context"],
26191
+ agentBehavior: "Create a focused research plan, use the host's approved public research tools, and save only cited proposed facts with an implication for the product. Keep direct user evidence, repo evidence, and market evidence distinct.",
26192
+ proof: "The task includes a research question, source URLs or repo paths, access dates, confidence, proposed/approved status, and open questions when evidence conflicts."
26193
+ },
25529
26194
  {
25530
26195
  id: "scope_change",
25531
26196
  label: "Scope changes",
@@ -26258,7 +26923,7 @@ function consoleLoopContract(scan) {
26258
26923
  `Use this when the current console cannot call MCP tools directly for ${scan.repoName}.`,
26259
26924
  "",
26260
26925
  "1. Ask the operator for a Brief context packet or to run `brief.context_for_task` for the task.",
26261
- "2. State the mission recipe you are following: feature, design_change, content_change, bugfix, ticket_write, ticket_fix, incident_fix, internal_app, stale_pr, merge_conflict, flaky_test, small_refactor, bug_investigation, qa_edge_cases, performance, porting, experiment, pr_review, session_review, session_library, research, docs_wiki, or dependency.",
26926
+ "2. State the mission recipe you are following: feature, design_change, content_change, bugfix, ticket_write, ticket_fix, incident_fix, internal_app, stale_pr, merge_conflict, flaky_test, small_refactor, bug_investigation, qa_edge_cases, web_dogfood, performance, porting, experiment, pr_review, session_review, session_library, research, docs_wiki, or dependency.",
26262
26927
  "3. Keep scope bounded to the cited files, owners, rules, tests, and wiki pages.",
26263
26928
  "4. Before suggesting handoff, list the validation commands to run and what each proves.",
26264
26929
  "5. Ask for `brief.review_diff`, `brief.save_handoff`, and `brief.verify_receipts` from an MCP-capable surface before PR review.",
@@ -26300,11 +26965,12 @@ function firstRunPromptsFor(label, scan) {
26300
26965
  "Ask the agent: Save one proven implementation reference with brief.save_reference, then list it with brief.reference_implementations.",
26301
26966
  "Ask the agent: Call brief.agent_pack_inventory and summarize skills, commands, hooks, MCP configs, host configs, installers, and high-risk trust boundaries.",
26302
26967
  "Ask the agent: Call brief.agent_pack_adoption_plan before importing a skill, command, or prompt pack.",
26968
+ 'For web app dogfooding: Call brief.mission_plan with recipe "web_dogfood", name 2-5 critical journeys and a safe starting state, then use the available browser and hand back the top fixes with evidence.',
26303
26969
  "For UI work: Call brief.design_system_map and use the team's templates, tokens, components, setup rules, and design_adherence gate.",
26304
26970
  "For a ticket: Call brief.fix_ticket with the work item, optional runtime evidence, and autonomy validate.",
26305
26971
  "For a runtime signal: Call brief.runtime_fix with the Sentry issue, Datadog trace, CI failure, log query, or incident id.",
26306
26972
  'For an internal analytics app: Call brief.mission_plan with recipe "internal_app", then follow the returned auth/data/access-control gates.',
26307
- 'For maintenance work: Call brief.mission_plan with recipe "stale_pr", "merge_conflict", "flaky_test", "small_refactor", "bug_investigation", "qa_edge_cases", "performance", "porting", or "experiment" before editing.',
26973
+ 'For maintenance work: Call brief.mission_plan with recipe "stale_pr", "merge_conflict", "flaky_test", "small_refactor", "bug_investigation", "qa_edge_cases", "web_dogfood", "performance", "porting", or "experiment" before editing.',
26308
26974
  "Before handoff: Call brief.review_diff, brief.save_handoff, and brief.verify_receipts so the final update includes review, proof bundle, and ledger status."
26309
26975
  ];
26310
26976
  }
@@ -26314,7 +26980,7 @@ function mcpJsonSnippet(installMode, briefSourceRoot, repoRoot) {
26314
26980
  args: ["-lc", localSourceMcpCommand(briefSourceRoot, repoRoot)]
26315
26981
  } : {
26316
26982
  command: "npx",
26317
- args: ["-y", briefPublishedPackageSpec]
26983
+ args: ["-y", briefPublishedPackageInstallSpec]
26318
26984
  };
26319
26985
  const env = {
26320
26986
  BRIEF_REPO_ROOT: repoRoot ?? "${workspaceFolder}",
@@ -26381,7 +27047,7 @@ function rolloutStepsFor(readiness) {
26381
27047
  "Promote the repo once rules, tests, owners, and memories are discoverable."
26382
27048
  ];
26383
27049
  }
26384
- var ALL_TARGETS, briefPublishedPackageSpec, TARGET_LABELS, INSTALL_COMMANDS, CONNECTOR_DESTINATIONS;
27050
+ var ALL_TARGETS, briefPublishedPackageVersion, briefPublishedPackageSpec, briefPublishedPackageInstallSpec, TARGET_LABELS, INSTALL_COMMANDS, CONNECTOR_DESTINATIONS;
26385
27051
  var init_agentSetup = __esm({
26386
27052
  "../core/dist/agentSetup.js"() {
26387
27053
  "use strict";
@@ -26399,7 +27065,9 @@ var init_agentSetup = __esm({
26399
27065
  "ide",
26400
27066
  "generic"
26401
27067
  ];
26402
- briefPublishedPackageSpec = "runbrief@0.1.3";
27068
+ briefPublishedPackageVersion = "0.1.4";
27069
+ briefPublishedPackageSpec = `runbrief@${briefPublishedPackageVersion}`;
27070
+ briefPublishedPackageInstallSpec = `runbrief@^${briefPublishedPackageVersion}`;
26403
27071
  TARGET_LABELS = {
26404
27072
  terminal: "Terminal agent",
26405
27073
  vscode: "VS Code",
@@ -26412,15 +27080,15 @@ var init_agentSetup = __esm({
26412
27080
  generic: "Any MCP client"
26413
27081
  };
26414
27082
  INSTALL_COMMANDS = {
26415
- terminal: `npx -y ${briefPublishedPackageSpec}`,
26416
- vscode: `npx -y ${briefPublishedPackageSpec}`,
26417
- claude: `claude mcp add --transport stdio --scope user brief -- npx -y ${briefPublishedPackageSpec}`,
27083
+ terminal: `npx -y ${briefPublishedPackageInstallSpec}`,
27084
+ vscode: `npx -y ${briefPublishedPackageInstallSpec}`,
27085
+ claude: `claude mcp add --transport stdio --scope user brief -- npx -y ${briefPublishedPackageInstallSpec}`,
26418
27086
  claude_console: "Paste the mission_plan loop recipe when direct MCP tools are unavailable.",
26419
- codex: `codex mcp add brief -- npx -y ${briefPublishedPackageSpec}`,
26420
- cursor: `npx -y ${briefPublishedPackageSpec}`,
27087
+ codex: `codex mcp add brief -- npx -y ${briefPublishedPackageInstallSpec}`,
27088
+ cursor: `npx -y ${briefPublishedPackageInstallSpec}`,
26421
27089
  cursor_console: "Paste the mission_plan loop recipe when direct MCP tools are unavailable.",
26422
- ide: `npx -y ${briefPublishedPackageSpec}`,
26423
- generic: `npx -y ${briefPublishedPackageSpec}`
27090
+ ide: `npx -y ${briefPublishedPackageInstallSpec}`,
27091
+ generic: `npx -y ${briefPublishedPackageInstallSpec}`
26424
27092
  };
26425
27093
  CONNECTOR_DESTINATIONS = {
26426
27094
  terminal: "Repo shell MCP server env",
@@ -26690,6 +27358,9 @@ function recipeForIntent(kind, request, explicitRecipe, runtimeEvidence) {
26690
27358
  return /\b(bug|broken|fix|incorrect|wrong)\b/i.test(request) ? "bugfix" : "feature";
26691
27359
  }
26692
27360
  const lower = request.toLowerCase();
27361
+ if (/\b(dogfood|dogfooding|exploratory browser|web app smoke|browser smoke|product safari|try the app|use the app)\b/.test(lower) && /\b(app|site|web|browser|page|screen|flow|journey|ui|ux|localhost|staging|production)\b/.test(lower)) {
27362
+ return "web_dogfood";
27363
+ }
26693
27364
  if (isDesignLedChange(lower)) {
26694
27365
  return "design_change";
26695
27366
  }
@@ -26726,7 +27397,7 @@ function isProductJourneyAudit(request) {
26726
27397
  return /\b(audit|assess|evaluate|inspect|study)\b/.test(request) && /\b(app|experience|flow|journey|launch readiness|onboarding|product|self[- ]serve|workspace)\b/.test(request);
26727
27398
  }
26728
27399
  function autonomyForIntent(kind, recipe) {
26729
- if (kind === "context_for_task" || kind === "write_ticket" || recipe === "research") {
27400
+ if (kind === "context_for_task" || kind === "write_ticket" || recipe === "research" || recipe === "web_dogfood") {
26730
27401
  return "read_only";
26731
27402
  }
26732
27403
  return "validate";
@@ -27387,6 +28058,9 @@ function summaryForIntent(kind, recipe, workItem, runtimeEvidence) {
27387
28058
  return "Brief inferred a compact pre-PR readiness pass.";
27388
28059
  if (kind === "context_for_task")
27389
28060
  return "Brief inferred a context-only preflight.";
28061
+ if (recipe === "web_dogfood") {
28062
+ return "Brief inferred a read-only browser dogfooding loop: exercise journeys, capture evidence, and rank the top user-impact fixes.";
28063
+ }
27390
28064
  return `Brief inferred a ${recipe ?? "mission"} loop.`;
27391
28065
  }
27392
28066
  function reasonForIntent(kind, request, workItem, runtimeEvidence) {
@@ -28686,7 +29360,7 @@ var init_governance = __esm({
28686
29360
  // ../core/dist/hookSetup.js
28687
29361
  function generateHookSetup(input = {}) {
28688
29362
  const mode = input.mode ?? "advisory";
28689
- const packageCommand = input.packageCommand ?? `npx -y ${briefPublishedPackageSpec}`;
29363
+ const packageCommand = input.packageCommand ?? `npx -y ${briefPublishedPackageInstallSpec}`;
28690
29364
  const failOn = mode === "blocking" ? "high" : "blocker";
28691
29365
  return {
28692
29366
  mode,
@@ -33294,6 +33968,7 @@ var init_dist2 = __esm({
33294
33968
  init_agentBeforeYouCode();
33295
33969
  init_agentWorkPacket();
33296
33970
  init_productOperatingContext();
33971
+ init_productIntelligence();
33297
33972
  init_ticketMissionRuntime();
33298
33973
  init_governance();
33299
33974
  init_hookSetup();
@@ -34929,9 +35604,9 @@ function buildConnectorRecoveryPlan(input) {
34929
35604
  });
34930
35605
  const restartNeeded = readiness.status === "restart_required" || readiness.status === "source_mismatch";
34931
35606
  const needsVerification = readiness.status === "needs_verification";
34932
- const canSelfRestart = input.mode === "server" && readiness.status !== "source_mismatch";
35607
+ const canSelfRestart = input.mode === "server" && readiness.status !== "wrong_repo";
34933
35608
  const confirmed = input.confirm === briefRestartConfirmation;
34934
- const shouldSchedule = canSelfRestart && confirmed && (readiness.status === "restart_required" || input.force === true);
35609
+ const shouldSchedule = canSelfRestart && confirmed && (readiness.status === "restart_required" || readiness.status === "source_mismatch" || input.force === true);
34935
35610
  const status = shouldSchedule ? "restart_scheduled" : restartNeeded ? readiness.status : needsVerification ? "needs_verification" : "ready";
34936
35611
  const verifyExpect = [
34937
35612
  "connectorReadiness.status is ready",
@@ -34955,7 +35630,7 @@ function buildConnectorRecoveryPlan(input) {
34955
35630
  mcpSelfRestart: {
34956
35631
  tool: "brief.restart_connector",
34957
35632
  available: canSelfRestart,
34958
- behavior: "When confirmed, Brief returns this recovery packet, waits briefly, then exits the local MCP process. MCP hosts that supervise subprocesses can relaunch it; hosts that do not should be restarted or reloaded by the user.",
35633
+ behavior: "When confirmed, Brief returns this recovery packet, waits briefly, then exits the local MCP process. MCP hosts that supervise subprocesses can relaunch it; hosts that do not should be restarted or reloaded by the user. A restart refreshes the configured source; it does not change a host's source path.",
34959
35634
  call: {
34960
35635
  confirm: briefRestartConfirmation,
34961
35636
  surface: selectedSurface,
@@ -34978,7 +35653,8 @@ function buildConnectorRecoveryPlan(input) {
34978
35653
  }
34979
35654
  function summaryForStatus(status, readiness) {
34980
35655
  if (status === "restart_scheduled") {
34981
- return "Brief scheduled the local MCP process to exit so the host can relaunch a fresh connector.";
35656
+ const restartSummary = "Brief scheduled the local MCP process to exit so the host can relaunch a fresh connector.";
35657
+ return readiness.status === "source_mismatch" ? `${restartSummary} ${readiness.summary} Verify the host source path after reconnecting.` : restartSummary;
34982
35658
  }
34983
35659
  return readiness.summary;
34984
35660
  }
@@ -35081,7 +35757,7 @@ function mcpServerFreshness(options2) {
35081
35757
  ...base,
35082
35758
  action: freshnessAction("current"),
35083
35759
  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."
35760
+ next: "Continue using Brief normally. Fresh MCP starts resolve the latest compatible patch; reload the host to pick it up, and use a new generated setup for a future minor or major release."
35085
35761
  };
35086
35762
  }
35087
35763
  return {
@@ -35384,14 +36060,16 @@ var init_serverInstructions = __esm({
35384
36060
  "../mcp/src/serverInstructions.ts"() {
35385
36061
  "use strict";
35386
36062
  briefMcpInstructions = [
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'.",
35388
- "At the first Brief-relevant task in a session, call brief.install_status once and confirm the repo target and connector freshness.",
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.",
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.",
35392
- "Refresh Brief context when the task, files, risk, or validation plan changes.",
36063
+ "Brief is the default repo-context, reuse, review, and handoff companion for non-trivial coding work. Do not wait for the user to repeat 'use Brief'.",
36064
+ "At the first task, call brief.install_status and confirm repo and connector freshness. Include task when known to get the first context packet.",
36065
+ "Local mode is the default and needs no account. Team mode adds approved shared guidance. If team sync is unavailable, continue local repo work and mention it only when it affects the task.",
36066
+ "Package-backed connectors pick up compatible patches on the next MCP start. Reload a long-lived host after an update; routine patches do not need a reinstall.",
36067
+ "For non-trivial work, call brief.next with the request before inspecting or editing and follow its compact recommendation. Auto-engage. Keep micro changes lightweight.",
36068
+ "Before creating a helper, component, route, workflow, command, or skill, use reuse candidates or call brief.reference_implementations and brief.codebase_map.",
36069
+ "For user, JTBD, competitor, market, taste, copy, or integration questions, call brief.product_research_plan, research with the host, then save cited proposed facts with brief.save_product_context.",
36070
+ "Refresh Brief when task, files, risk, or validation changes.",
35393
36071
  "Before a meaningful handoff, call brief.review_diff. Save and verify a handoff only for multi-step, high-risk, or durable work.",
35394
- "Keep Brief quiet in chat: report one concrete way it helped and the validation or remaining risk in one or two lines. Never invent Brief usage or paste full receipts unless asked."
36072
+ "Keep Brief quiet: report one concrete benefit and validation or risk in one or two lines. Never invent usage or paste receipts unless asked."
35395
36073
  ].join("\n");
35396
36074
  briefMcpInstructionSignals = [
35397
36075
  "brief.install_status",
@@ -35437,6 +36115,16 @@ function compactInstallStatus(input) {
35437
36115
  const mcpContractActive = input.serverInstructionsActive && input.connectorReadiness.safeToUseBrief;
35438
36116
  const teamSyncStatus = input.cloudSync.configured ? input.teamContext.status === "failed" ? "unavailable" : input.teamContext.status === "fetched" ? "connected" : "checking" : "not_connected";
35439
36117
  const connectionMode = teamSyncStatus === "unavailable" ? "team_degraded" : teamSyncStatus === "connected" ? "team" : "local";
36118
+ const connectorUpdate = input.serverFreshness.sourceKind === "package" ? {
36119
+ policy: "automatic_compatible_patches",
36120
+ installSpec: briefPublishedPackageInstallSpec,
36121
+ applies: "next_mcp_start",
36122
+ summary: "Package-backed connectors pick up the latest compatible patch when the MCP host starts them again."
36123
+ } : {
36124
+ policy: "manual_source_restart",
36125
+ applies: "host_restart",
36126
+ summary: "Source-checkout connectors use the files in their configured checkout; restart the host after local Brief changes."
36127
+ };
35440
36128
  const totalMapCandidates = stats.mapTotalCandidateCount ?? stats.mapNodeCount + (stats.mapOmittedCount ?? 0);
35441
36129
  const mapCoveragePercent = totalMapCandidates ? Math.round(stats.mapNodeCount / totalMapCandidates * 100) : 100;
35442
36130
  const repoGuidanceSignals = unique4(
@@ -35467,6 +36155,7 @@ function compactInstallStatus(input) {
35467
36155
  fresh: input.serverFreshness.status === "current",
35468
36156
  distribution: input.serverFreshness.sourceKind,
35469
36157
  sourceRoot: input.serverFreshness.sourceRoot,
36158
+ update: connectorUpdate,
35470
36159
  summary: input.connectorReadiness.summary
35471
36160
  },
35472
36161
  connection: {
@@ -35530,8 +36219,9 @@ var briefConnectorVersion;
35530
36219
  var init_installStatus = __esm({
35531
36220
  "../mcp/src/installStatus.ts"() {
35532
36221
  "use strict";
36222
+ init_dist2();
35533
36223
  init_serverInstructions();
35534
- briefConnectorVersion = "0.1.3";
36224
+ briefConnectorVersion = "0.1.4";
35535
36225
  }
35536
36226
  });
35537
36227
 
@@ -36024,7 +36714,7 @@ function renderWikiRefreshWorkflow(options2 = {}) {
36024
36714
  " with:",
36025
36715
  " node-version: 22",
36026
36716
  " - name: Refresh wiki",
36027
- ' run: npx -y runbrief@0.1.3 wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files',
36717
+ ` run: npx -y ${briefPublishedPackageInstallSpec} wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files`,
36028
36718
  " - name: Open refresh PR",
36029
36719
  " uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11",
36030
36720
  " with:",
@@ -36140,8 +36830,8 @@ function bestMatchingReceipt(receipts, kind, input) {
36140
36830
  function missionIntentMatches(receipt, input) {
36141
36831
  if (input.recipeId && receipt.missionRecipeId === input.recipeId) return true;
36142
36832
  if (!input.task) return false;
36143
- const wantedTask = normalizeText(input.task);
36144
- const receiptTask = normalizeText(receipt.task);
36833
+ const wantedTask = normalizeText2(input.task);
36834
+ const receiptTask = normalizeText2(receipt.task);
36145
36835
  return receiptTask === wantedTask || wantedTask.length >= 20 && receiptTask.length >= 20 && (receiptTask.includes(wantedTask) || wantedTask.includes(receiptTask));
36146
36836
  }
36147
36837
  function matchScore(receipt, input) {
@@ -36151,8 +36841,8 @@ function matchScore(receipt, input) {
36151
36841
  }
36152
36842
  let score = input.branch && receiptBranch2 ? 15 : 0;
36153
36843
  if (input.task) {
36154
- const wantedTask = normalizeText(input.task);
36155
- const receiptTask = normalizeText(receipt.task);
36844
+ const wantedTask = normalizeText2(input.task);
36845
+ const receiptTask = normalizeText2(receipt.task);
36156
36846
  if (receiptTask === wantedTask) {
36157
36847
  score += 80;
36158
36848
  } else if (wantedTask.length >= 20 && receiptTask.length >= 20 && (receiptTask.includes(wantedTask) || wantedTask.includes(receiptTask))) {
@@ -36210,7 +36900,7 @@ function cleanValues(values, limit = 300) {
36210
36900
  function unique5(values) {
36211
36901
  return [...new Set(values)];
36212
36902
  }
36213
- function normalizeText(value) {
36903
+ function normalizeText2(value) {
36214
36904
  return value.trim().toLowerCase().replace(/\s+/g, " ");
36215
36905
  }
36216
36906
  function normalizeBranch(value) {
@@ -36634,6 +37324,54 @@ function summarizePacket(packet) {
36634
37324
  ...packet.dependencyRemediation ? { dependencyRemediation: packet.dependencyRemediation } : {}
36635
37325
  };
36636
37326
  }
37327
+ async function buildActivationContext(input) {
37328
+ const request = {
37329
+ task: input.task,
37330
+ repoRoot: input.repoRoot,
37331
+ agent: "mcp-activation",
37332
+ maxTokens: Math.min(input.maxTokens ?? 6e3, 6e3)
37333
+ };
37334
+ if (input.files) request.files = input.files;
37335
+ if (input.branch) request.branch = input.branch;
37336
+ const packet = contextForTask(input.scan, request);
37337
+ const localReceipt = saveLocalContextReceipt(input.repoRoot, packet, {
37338
+ kind: "context",
37339
+ agent: "mcp-activation",
37340
+ ...input.files ? { files: input.files } : {},
37341
+ ...input.branch ? { branch: input.branch } : {}
37342
+ });
37343
+ const cloudSync = await syncContextReceipt(packet, input.repoRoot, {
37344
+ agent: "mcp-activation",
37345
+ ...input.files ? { files: input.files } : {},
37346
+ ...input.branch ? { branch: input.branch } : {}
37347
+ });
37348
+ return {
37349
+ status: "context_ready",
37350
+ task: input.task,
37351
+ packetId: packet.packetId,
37352
+ summary: packet.summary,
37353
+ tokenEstimate: packet.tokenEstimate,
37354
+ inspectFirst: packet.map.slice(0, 5).map((node) => ({
37355
+ path: node.path,
37356
+ kind: node.type,
37357
+ name: node.name
37358
+ })),
37359
+ rules: packet.rules.slice(0, 4).map((rule) => ({
37360
+ text: rule.body,
37361
+ sources: rule.sourcePaths
37362
+ })),
37363
+ suggestedTests: packet.suggestedTests.slice(0, 4),
37364
+ sources: packet.sources.slice(0, 6),
37365
+ localReceipt: {
37366
+ id: localReceipt.id
37367
+ },
37368
+ cloudSync: {
37369
+ status: cloudSync.status,
37370
+ ..."reason" in cloudSync && cloudSync.reason ? { reason: cloudSync.reason } : {}
37371
+ },
37372
+ next: "Use this packet before editing. If the task or files change, call brief.next again."
37373
+ };
37374
+ }
36637
37375
  function summarizeReview(review, detail = "auto") {
36638
37376
  if (detail === "auto" && isLightweightReview(review)) {
36639
37377
  return summarizeLightweightReview(review);
@@ -37241,15 +37979,17 @@ var init_server = __esm({
37241
37979
  init_workspace();
37242
37980
  init_handoffAutofill();
37243
37981
  init_scanFreshness();
37244
- cloudBootstrap = await bootstrapCloudCredentials();
37982
+ cloudBootstrap = process.env.BRIEF_CLOUD_BOOTSTRAP_DONE === "1" ? { status: "skipped", reason: "already_bootstrapped" } : await bootstrapCloudCredentials();
37983
+ process.env.BRIEF_CLOUD_BOOTSTRAP_DONE = "1";
37245
37984
  if (cloudBootstrap.status === "failed") {
37246
- console.error(`Brief cloud setup: ${cloudBootstrap.reason}`);
37247
- process.exit(1);
37985
+ console.error(
37986
+ `Brief team sync is unavailable: ${cloudBootstrap.reason} Continuing in local mode.`
37987
+ );
37248
37988
  }
37249
37989
  server = new McpServer(
37250
37990
  {
37251
37991
  name: "brief",
37252
- version: "0.1.3"
37992
+ version: "0.1.4"
37253
37993
  },
37254
37994
  {
37255
37995
  instructions: briefMcpInstructions
@@ -37349,6 +38089,7 @@ var init_server = __esm({
37349
38089
  "small_refactor",
37350
38090
  "bug_investigation",
37351
38091
  "qa_edge_cases",
38092
+ "web_dogfood",
37352
38093
  "performance",
37353
38094
  "porting",
37354
38095
  "experiment",
@@ -37389,14 +38130,20 @@ var init_server = __esm({
37389
38130
  ]);
37390
38131
  registerTool(
37391
38132
  "install_status",
37392
- "First Brief call once per agent session. Confirms MCP is connected to the correct repo/worktree, then points the agent toward brief.next for the actual task.",
38133
+ "First Brief call once per agent session. Confirms MCP is connected to the correct repo/worktree, scans it, and optionally returns the first compact context packet when task is provided. Never needs a reusable API key.",
37393
38134
  {
37394
38135
  repoPath: z2.string().optional().describe(
37395
38136
  "Path inside the repo. Defaults to BRIEF_REPO_ROOT captured from the agent workspace, then current working directory."
37396
38137
  ),
37397
- detail: responseDetailSchema
38138
+ detail: responseDetailSchema,
38139
+ task: z2.string().min(4).optional().describe(
38140
+ "The user's current task, when already known. Brief returns a compact first context packet in this same call."
38141
+ ),
38142
+ files: z2.array(z2.string()).optional(),
38143
+ branch: z2.string().optional(),
38144
+ maxTokens: z2.number().int().min(500).max(12e3).optional()
37398
38145
  },
37399
- async ({ repoPath, detail }) => {
38146
+ async ({ repoPath, detail, task, files, branch, maxTokens }) => {
37400
38147
  const rootResolution = resolveRepoRootDetails(repoPath);
37401
38148
  const repoRoot = rootResolution.repoRoot;
37402
38149
  const repoTarget = repoTargetHealth(rootResolution);
@@ -37420,8 +38167,16 @@ var init_server = __esm({
37420
38167
  );
37421
38168
  }
37422
38169
  const { scan, teamContext } = await scanRepoWithTeamContext(repoRoot, {
37423
- maxFiles: 400
38170
+ maxFiles: 1200
37424
38171
  });
38172
+ const activation = task?.trim() ? await buildActivationContext({
38173
+ scan,
38174
+ repoRoot,
38175
+ task: task.trim(),
38176
+ ...files ? { files } : {},
38177
+ ...branch ? { branch } : {},
38178
+ ...maxTokens !== void 0 ? { maxTokens } : {}
38179
+ }) : void 0;
37425
38180
  const report = buildLocalValueReport(repoRoot, scan.repoName);
37426
38181
  const setups = generateAgentSetup(scan, { target: "all" });
37427
38182
  const skillLearning = buildRepoSkillLearningReport(repoRoot, {
@@ -37448,6 +38203,7 @@ var init_server = __esm({
37448
38203
  skillLearning: summarizeSkillLearningForStart(skillLearning, void 0),
37449
38204
  connectorHealth: summarizeAgentConnectorHealth(setups),
37450
38205
  proofLoop: report.proofLoop,
38206
+ ...activation ? { activation } : {},
37451
38207
  value: {
37452
38208
  receiptCount: report.receiptCount,
37453
38209
  contextPacketCount: report.contextPacketCount,
@@ -37460,8 +38216,8 @@ var init_server = __esm({
37460
38216
  next: report.proofLoop.next
37461
38217
  };
37462
38218
  if (detail === "expanded") return jsonText(fullStatus);
37463
- return jsonText(
37464
- compactInstallStatus({
38219
+ return jsonText({
38220
+ ...compactInstallStatus({
37465
38221
  mode: fullStatus.mode,
37466
38222
  repoRoot,
37467
38223
  rootResolution,
@@ -37475,8 +38231,9 @@ var init_server = __esm({
37475
38231
  workflowWiring,
37476
38232
  valueReport: report,
37477
38233
  serverInstructionsActive: true
37478
- })
37479
- );
38234
+ }),
38235
+ ...activation ? { activation } : {}
38236
+ });
37480
38237
  }
37481
38238
  );
37482
38239
  registerTool(
@@ -38700,6 +39457,109 @@ var init_server = __esm({
38700
39457
  });
38701
39458
  }
38702
39459
  );
39460
+ registerTool(
39461
+ "product_research_plan",
39462
+ "Create a focused, source-safe research plan for a product, UX, content, market, competitor, or integration decision. Use the host's approved web/browser tools to investigate it, then save only cited observations with brief.save_product_context.",
39463
+ {
39464
+ task: z2.string().min(4).describe("The product decision or user job to research."),
39465
+ audience: z2.string().max(200).optional(),
39466
+ competitors: z2.array(z2.string().trim().min(1).max(120)).max(8).optional(),
39467
+ integrations: z2.array(z2.string().trim().min(1).max(120)).max(8).optional()
39468
+ },
39469
+ async ({ task, audience, competitors, integrations }) => jsonText({
39470
+ ...buildProductResearchPlan({
39471
+ task,
39472
+ ...audience ? { audience } : {},
39473
+ ...competitors ? { competitors } : {},
39474
+ ...integrations ? { integrations } : {}
39475
+ }),
39476
+ next: "Use the host's approved public web/browser research. Do not treat search snippets as product truth; save the source URL, access date, claim, implication, and confidence with brief.save_product_context."
39477
+ })
39478
+ );
39479
+ registerTool(
39480
+ "save_product_context",
39481
+ "Save cited product intelligence locally for this repo. Facts remain proposed by default; use approved only after a human product decision. Store JTBD, personas, product principles, terminology, competitor/integration observations, metrics, and decisions without credentials or private source content.",
39482
+ {
39483
+ repoPath: z2.string().optional().describe(
39484
+ "Path inside the repo. Defaults to BRIEF_REPO_ROOT captured from the agent workspace, then current working directory."
39485
+ ),
39486
+ repoName: z2.string().trim().max(160).optional(),
39487
+ facts: z2.array(
39488
+ z2.object({
39489
+ kind: z2.enum([
39490
+ "persona",
39491
+ "jtbd",
39492
+ "principle",
39493
+ "terminology",
39494
+ "competitor",
39495
+ "integration",
39496
+ "metric",
39497
+ "decision"
39498
+ ]),
39499
+ statement: z2.string().trim().min(8).max(1e3),
39500
+ appliesTo: z2.array(z2.string().trim().min(1).max(240)).max(24).optional(),
39501
+ status: z2.enum(["proposed", "approved", "stale", "rejected"]).optional(),
39502
+ confidence: z2.number().min(0).max(1).optional(),
39503
+ sources: z2.array(
39504
+ z2.object({
39505
+ kind: z2.enum(["repo", "user", "research", "feedback", "dogfood"]),
39506
+ label: z2.string().trim().min(1).max(180),
39507
+ url: z2.string().url().optional(),
39508
+ path: z2.string().trim().max(300).optional(),
39509
+ accessedAt: z2.string().datetime().optional(),
39510
+ excerpt: z2.string().trim().max(420).optional()
39511
+ })
39512
+ ).max(12).optional()
39513
+ })
39514
+ ).min(1).max(40),
39515
+ openQuestions: z2.array(z2.string().trim().min(4).max(300)).max(40).optional(),
39516
+ decisions: z2.array(z2.string().trim().min(4).max(500)).max(80).optional()
39517
+ },
39518
+ async ({
39519
+ repoPath,
39520
+ repoName,
39521
+ facts,
39522
+ openQuestions,
39523
+ decisions
39524
+ }) => {
39525
+ const repoTargetInfo = resolveRepoTarget(repoPath);
39526
+ if (repoTargetInfo.repoTarget.status === "needs_repo_target") {
39527
+ return repoTargetBlock(repoTargetInfo, { facts: facts.length });
39528
+ }
39529
+ const { repoRoot, rootResolution, repoTarget } = repoTargetInfo;
39530
+ try {
39531
+ const result = saveProductIntelligence(repoRoot, {
39532
+ ...repoName ? { repoName } : {},
39533
+ facts,
39534
+ ...openQuestions ? { openQuestions } : {},
39535
+ ...decisions ? { decisions } : {}
39536
+ });
39537
+ return jsonText({
39538
+ status: "saved_product_context",
39539
+ repoRoot,
39540
+ rootResolution,
39541
+ repoTarget,
39542
+ savedFactIds: result.savedFactIds,
39543
+ factCount: result.store.facts.length,
39544
+ sourceCount: new Set(
39545
+ result.store.facts.flatMap(
39546
+ (fact) => fact.sources.map((source) => source.id)
39547
+ )
39548
+ ).size,
39549
+ next: "Call brief.product_context for a task to see the relevant facts. Proposed facts need human review before becoming team defaults."
39550
+ });
39551
+ } catch (error) {
39552
+ return jsonText({
39553
+ status: "product_context_save_failed",
39554
+ repoRoot,
39555
+ rootResolution,
39556
+ repoTarget,
39557
+ error: error instanceof Error ? error.message : "Could not save product context.",
39558
+ next: "Fix the source citation or repository target and retry; no context was saved from this request."
39559
+ });
39560
+ }
39561
+ }
39562
+ );
38703
39563
  registerTool(
38704
39564
  "context_for_task",
38705
39565
  "Return a compact, source-grounded context packet after brief.next or brief.start identifies that scoped context is needed. Use when scope changes, files are unclear, or an agent needs rules/tests/map evidence before continuing.",
@@ -39827,6 +40687,7 @@ var init_server = __esm({
39827
40687
  "small_refactor",
39828
40688
  "bug_investigation",
39829
40689
  "qa_edge_cases",
40690
+ "web_dogfood",
39830
40691
  "performance",
39831
40692
  "porting",
39832
40693
  "experiment",
@@ -40137,6 +40998,7 @@ var cliStartedAt = /* @__PURE__ */ new Date();
40137
40998
  var cliModulePath = fileURLToPath2(import.meta.url);
40138
40999
  if (!isCloudLogoutCommand(command)) {
40139
41000
  const cloudBootstrap2 = await bootstrapCloudCredentials();
41001
+ process.env.BRIEF_CLOUD_BOOTSTRAP_DONE = "1";
40140
41002
  if (cloudBootstrap2.status === "failed") {
40141
41003
  console.error(
40142
41004
  `Brief team sync is unavailable: ${cloudBootstrap2.reason} Continuing in local mode.`
@@ -40320,6 +41182,66 @@ async function runCli(commandName, commandArgs) {
40320
41182
  process.exitCode = 1;
40321
41183
  return;
40322
41184
  }
41185
+ if (isCommand(
41186
+ commandName,
41187
+ "product-research-plan",
41188
+ "product_research_plan",
41189
+ "research-product"
41190
+ )) {
41191
+ const task = option2(commandArgs, "task") ?? positionalText(commandArgs);
41192
+ if (!task) {
41193
+ fail(
41194
+ 'Missing task for product-research-plan. Example: brief product-research-plan --repo "$PWD" "Choose the right issue-triage integration"'
41195
+ );
41196
+ }
41197
+ const audience = option2(commandArgs, "audience");
41198
+ const competitors = textListOption(commandArgs, "competitor");
41199
+ const integrations = textListOption(commandArgs, "integration");
41200
+ printJson(
41201
+ buildProductResearchPlan({
41202
+ task,
41203
+ ...audience ? { audience } : {},
41204
+ ...competitors ? { competitors } : {},
41205
+ ...integrations ? { integrations } : {}
41206
+ })
41207
+ );
41208
+ return;
41209
+ }
41210
+ if (isCommand(
41211
+ commandName,
41212
+ "save-product-context",
41213
+ "save_product_context",
41214
+ "product-context-save"
41215
+ )) {
41216
+ const factsFile = option2(commandArgs, "facts-file");
41217
+ if (!factsFile) {
41218
+ fail(
41219
+ 'Missing --facts-file. Pass a JSON file with { "facts": [{ "kind", "statement", "sources" }] } so citations stay reviewable.'
41220
+ );
41221
+ }
41222
+ let parsed;
41223
+ try {
41224
+ parsed = JSON.parse(readFileSync7(path20.resolve(repoRoot, factsFile), "utf8"));
41225
+ } catch (error) {
41226
+ fail(
41227
+ `Could not read --facts-file: ${error instanceof Error ? error.message : "invalid JSON"}`
41228
+ );
41229
+ }
41230
+ if (!parsed?.facts || !Array.isArray(parsed.facts)) {
41231
+ fail('The --facts-file must contain a top-level "facts" array.');
41232
+ }
41233
+ const result = saveProductIntelligence(repoRoot, parsed);
41234
+ printJson({
41235
+ status: "saved_product_context",
41236
+ repoRoot,
41237
+ rootResolution,
41238
+ repoTarget,
41239
+ savedFactIds: result.savedFactIds,
41240
+ factCount: result.store.facts.length,
41241
+ next: "Run brief product-context for a task to receive the relevant cited product facts."
41242
+ });
41243
+ return;
41244
+ }
40323
41245
  if (isCommand(
40324
41246
  commandName,
40325
41247
  "pre-pr",
@@ -42123,6 +43045,36 @@ function cliCommandHelp() {
42123
43045
  "When the agent needs acceptance criteria and state checks before editing."
42124
43046
  ]
42125
43047
  },
43048
+ {
43049
+ command: "product-research-plan",
43050
+ aliases: ["product_research_plan", "research-product"],
43051
+ purpose: "Turn a product, user, competitor, taste, or integration question into a focused research plan with source and approval rules.",
43052
+ repoBound: true,
43053
+ usage: 'brief product-research-plan --repo $PWD "Choose the right issue-triage integration"',
43054
+ examples: [
43055
+ 'brief product-research-plan --repo $PWD "Improve onboarding copy for coding agents"',
43056
+ 'brief product-research-plan --repo $PWD --competitor Cursor --competitor Factory "Improve agent context"'
43057
+ ],
43058
+ whenToUse: [
43059
+ "When a PM, designer, or agent needs current market, competitor, integration, or user evidence.",
43060
+ "Before turning external research into a product rule, skill, style, or copy decision."
43061
+ ]
43062
+ },
43063
+ {
43064
+ command: "save-product-context",
43065
+ aliases: ["save_product_context", "product-context-save"],
43066
+ purpose: "Save cited product intelligence locally so future product, design, copy, and integration work receives relevant context.",
43067
+ repoBound: true,
43068
+ usage: "brief save-product-context --repo $PWD --facts-file /tmp/brief-product-facts.json",
43069
+ examples: [
43070
+ "brief save-product-context --repo $PWD --facts-file ./product-facts.json"
43071
+ ],
43072
+ whenToUse: [
43073
+ "After researching with an approved browser or web source.",
43074
+ "After a user, dogfood session, or product decision produces a reusable fact.",
43075
+ "Facts remain proposed by default until a human approves them."
43076
+ ]
43077
+ },
42126
43078
  {
42127
43079
  command: "map",
42128
43080
  aliases: ["codebase-map", "codebase_map"],
@@ -42381,6 +43333,15 @@ function positionalText(argsList) {
42381
43333
  "doNotCopy",
42382
43334
  "owner",
42383
43335
  "receipts",
43336
+ "audience",
43337
+ "competitor",
43338
+ "competitors",
43339
+ "integration",
43340
+ "integrations",
43341
+ "facts-file",
43342
+ "repo-name",
43343
+ "open-questions",
43344
+ "decisions",
42384
43345
  "added-by",
42385
43346
  "addedBy",
42386
43347
  "agent-command",
@@ -42503,6 +43464,7 @@ function recipeOption(argsList) {
42503
43464
  "small_refactor",
42504
43465
  "bug_investigation",
42505
43466
  "qa_edge_cases",
43467
+ "web_dogfood",
42506
43468
  "performance",
42507
43469
  "porting",
42508
43470
  "experiment",