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.
- package/README.md +9 -4
- package/dist/cli.js +1061 -99
- 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(
|
|
5476
|
+
function sourceDocument(pathName2, type, content) {
|
|
5476
5477
|
return {
|
|
5477
|
-
id: idFor("src", `${
|
|
5478
|
-
path:
|
|
5478
|
+
id: idFor("src", `${pathName2}:${content}`),
|
|
5479
|
+
path: pathName2,
|
|
5479
5480
|
type,
|
|
5480
|
-
title: titleFromPath(
|
|
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,
|
|
7626
|
+
function mapNode(type, pathName2, summary, tags, metadata) {
|
|
7626
7627
|
const node = {
|
|
7627
|
-
id: idFor("map", `${type}:${
|
|
7628
|
+
id: idFor("map", `${type}:${pathName2}`),
|
|
7628
7629
|
type,
|
|
7629
|
-
name: titleFromPath(
|
|
7630
|
-
path:
|
|
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(
|
|
13477
|
-
return /(^|\/|\.)(test|spec)\.[^.]+$/i.test(
|
|
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." :
|
|
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:
|
|
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
|
-
|
|
18137
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
18145
|
-
|
|
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 ["
|
|
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") &&
|
|
21164
|
-
score +=
|
|
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",
|
|
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
|
-
|
|
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 ${
|
|
26416
|
-
vscode: `npx -y ${
|
|
26417
|
-
claude: `claude mcp add --transport stdio --scope user brief -- npx -y ${
|
|
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 ${
|
|
26420
|
-
cursor: `npx -y ${
|
|
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 ${
|
|
26423
|
-
generic: `npx -y ${
|
|
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 ${
|
|
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 !== "
|
|
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
|
-
|
|
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.
|
|
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.
|
|
35388
|
-
"At the first
|
|
35389
|
-
"Local mode is the default and
|
|
35390
|
-
"
|
|
35391
|
-
"
|
|
35392
|
-
"
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
36144
|
-
const receiptTask =
|
|
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 =
|
|
36155
|
-
const receiptTask =
|
|
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
|
|
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(
|
|
37247
|
-
|
|
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.
|
|
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,
|
|
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:
|
|
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",
|