runbrief 0.1.2 → 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 +30 -3
- package/dist/cli.js +2341 -680
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ var __esm = (fn, res) => function __init() {
|
|
|
6
6
|
|
|
7
7
|
// ../contracts/dist/index.js
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
var privacyModeSchema, enforcementSchema, skillStatusSchema, learningStatusSchema, findingSeveritySchema, prReadinessVerdictSchema, preCodingContextProofSchema, prReadinessCheckStatusSchema, prReadinessGateIdSchema, prReadinessGateStatusSchema, prReadinessStatusSchema, prReadinessDeliveryModeSchema, githubCheckConclusionSchema, agentSurfaceModeSchema, agentSurfaceCategorySchema, agentSurfaceCredentialStorageSchema, agentSurfaceRepoResolutionSchema, agentSurfaceTrustBoundarySchema, briefProofStateSchema, briefToolNameSchema, agentSurfaceContractSchema, briefRunPhaseSchema, briefRunContractSchema, briefProofContractSchema, repoRefSchema, scanStatsSchema, sourceDocumentSchema, ruleSchema, betaReadinessSchema, skillSchema, mapNodeSchema, reviewMemorySchema, wikiEvidenceItemSchema, wikiPageTypeSchema, wikiPageEvidenceMixSchema, wikiPageEntitySchema, wikiPageFreshnessSchema, wikiPageSourceProofSchema, wikiPageAgentBriefSchema, wikiPageQualitySchema, wikiPageSchema, prReadinessFindingSchema, prReadinessPullRequestSchema, prReadinessContextProofInputSchema, prReadinessCheckSchema, prReadinessGateResultSchema, prReadinessContractSchema, prReadinessReportRequestSchema, severityCountsSchema, prReadinessAnnotationSchema, prReadinessGithubCheckSchema, prReadinessReportSchema, prReadinessReportResponseSchema, repoScanSummarySchema, syncScanRequestSchema, teamGuidanceItemSchema, teamGuidanceDeliverySchema, contextPacketSchema, contextReceiptRequestSchema, missionRecipeIdSchema, missionWorkItemSchema, missionRuntimeEvidenceSchema, runtimeEvidencePullInstructionSchema, runtimeEvidencePacketSchema, missionHandoffValidationSchema, missionHandoffReviewReadinessSchema, missionHandoffReviewSchema, missionHandoffProofStatusSchema, missionHandoffValidationSummarySchema, missionHandoffArtifactEvidenceSchema, missionHandoffArtifactProofSchema, missionHandoffChangedFileAttributionSchema, missionHandoffProofBundleSchema, missionHandoffRequestSchema, evidenceRedactionFindingSchema, evidenceRedactionReportSchema, learningCreateRequestSchema, teamContextRequestSchema, teamContextResponseSchema, apiAcceptedResponseSchema;
|
|
9
|
+
var privacyModeSchema, loopRunStatusSchema, loopRunProofSchema, loopRunSummarySchema, createLoopRunRequestSchema, claimLoopRunRequestSchema, updateLoopRunRequestSchema, enforcementSchema, skillStatusSchema, learningStatusSchema, findingSeveritySchema, prReadinessVerdictSchema, preCodingContextProofSchema, prReadinessCheckStatusSchema, prReadinessGateIdSchema, prReadinessGateStatusSchema, prReadinessStatusSchema, prReadinessDeliveryModeSchema, githubCheckConclusionSchema, agentSurfaceModeSchema, agentSurfaceCategorySchema, agentSurfaceCredentialStorageSchema, agentSurfaceRepoResolutionSchema, agentSurfaceTrustBoundarySchema, briefProofStateSchema, briefToolNameSchema, agentSurfaceContractSchema, briefRunPhaseSchema, briefRunContractSchema, briefProofContractSchema, repoRefSchema, scanStatsSchema, sourceDocumentSchema, ruleSchema, betaReadinessSchema, skillSchema, mapNodeSchema, reviewMemorySchema, wikiEvidenceItemSchema, wikiPageTypeSchema, wikiPageEvidenceMixSchema, wikiPageEntitySchema, wikiPageFreshnessSchema, wikiPageSourceProofSchema, wikiPageAgentBriefSchema, wikiPageQualitySchema, wikiPageSchema, prReadinessFindingSchema, prReadinessPullRequestSchema, prReadinessContextProofInputSchema, prReadinessCheckSchema, prReadinessGateResultSchema, prReadinessContractSchema, prReadinessReportRequestSchema, severityCountsSchema, prReadinessAnnotationSchema, prReadinessGithubCheckSchema, prReadinessReportSchema, prReadinessReportResponseSchema, repoScanSummarySchema, syncScanRequestSchema, teamGuidanceItemSchema, teamGuidanceDeliverySchema, dependencyPackageSignalSchema, dependencyRemediationSchema, contextPacketSchema, contextReceiptRequestSchema, missionRecipeIdSchema, missionWorkItemSchema, missionRuntimeEvidenceSchema, runtimeEvidencePullInstructionSchema, runtimeEvidencePacketSchema, missionHandoffValidationSchema, missionHandoffReviewReadinessSchema, missionHandoffReviewSchema, missionHandoffProofStatusSchema, missionHandoffValidationSummarySchema, missionHandoffArtifactEvidenceSchema, missionHandoffArtifactProofSchema, missionHandoffChangedFileAttributionSchema, missionHandoffProofBundleSchema, missionHandoffRequestSchema, evidenceRedactionFindingSchema, evidenceRedactionReportSchema, learningCreateRequestSchema, teamContextRequestSchema, teamContextResponseSchema, apiAcceptedResponseSchema;
|
|
10
10
|
var init_dist = __esm({
|
|
11
11
|
"../contracts/dist/index.js"() {
|
|
12
12
|
"use strict";
|
|
@@ -15,6 +15,56 @@ var init_dist = __esm({
|
|
|
15
15
|
"cloud-sync-derived",
|
|
16
16
|
"team-cloud"
|
|
17
17
|
]);
|
|
18
|
+
loopRunStatusSchema = z.enum([
|
|
19
|
+
"queued",
|
|
20
|
+
"running",
|
|
21
|
+
"succeeded",
|
|
22
|
+
"failed",
|
|
23
|
+
"cancelled"
|
|
24
|
+
]);
|
|
25
|
+
loopRunProofSchema = z.record(z.string(), z.unknown());
|
|
26
|
+
loopRunSummarySchema = z.object({
|
|
27
|
+
id: z.string().uuid(),
|
|
28
|
+
workspaceId: z.string().uuid(),
|
|
29
|
+
loopId: z.string().uuid(),
|
|
30
|
+
loopName: z.string().min(1),
|
|
31
|
+
loopBody: z.string().min(1),
|
|
32
|
+
guardrails: z.array(z.string().min(1)).max(30),
|
|
33
|
+
loopVersion: z.number().int().positive(),
|
|
34
|
+
task: z.string().min(1).max(12e3),
|
|
35
|
+
repoFullName: z.string().min(1).max(240).optional(),
|
|
36
|
+
autonomy: z.enum(["read_only", "repo_edit", "validate", "handoff"]),
|
|
37
|
+
status: loopRunStatusSchema,
|
|
38
|
+
attemptCount: z.number().int().nonnegative(),
|
|
39
|
+
runnerId: z.string().min(1).max(160).nullable(),
|
|
40
|
+
leaseExpiresAt: z.string().datetime().nullable(),
|
|
41
|
+
lastEvent: z.string().max(2e3).nullable(),
|
|
42
|
+
resultSummary: z.string().max(12e3).nullable(),
|
|
43
|
+
proof: loopRunProofSchema,
|
|
44
|
+
errorMessage: z.string().max(4e3).nullable(),
|
|
45
|
+
createdAt: z.string().datetime(),
|
|
46
|
+
startedAt: z.string().datetime().nullable(),
|
|
47
|
+
completedAt: z.string().datetime().nullable()
|
|
48
|
+
});
|
|
49
|
+
createLoopRunRequestSchema = z.object({
|
|
50
|
+
loopId: z.string().uuid(),
|
|
51
|
+
task: z.string().trim().min(1).max(12e3),
|
|
52
|
+
repoFullName: z.string().trim().min(1).max(240).optional(),
|
|
53
|
+
idempotencyKey: z.string().trim().min(16).max(160)
|
|
54
|
+
});
|
|
55
|
+
claimLoopRunRequestSchema = z.object({
|
|
56
|
+
runnerId: z.string().trim().min(1).max(160),
|
|
57
|
+
leaseSeconds: z.number().int().min(30).max(900).default(180)
|
|
58
|
+
});
|
|
59
|
+
updateLoopRunRequestSchema = z.object({
|
|
60
|
+
runnerId: z.string().trim().min(1).max(160),
|
|
61
|
+
status: loopRunStatusSchema,
|
|
62
|
+
lastEvent: z.string().trim().max(2e3).optional(),
|
|
63
|
+
resultSummary: z.string().trim().max(12e3).optional(),
|
|
64
|
+
proof: loopRunProofSchema.optional(),
|
|
65
|
+
errorMessage: z.string().trim().max(4e3).optional(),
|
|
66
|
+
leaseSeconds: z.number().int().min(30).max(900).optional()
|
|
67
|
+
});
|
|
18
68
|
enforcementSchema = z.enum([
|
|
19
69
|
"advisory",
|
|
20
70
|
"warn",
|
|
@@ -621,6 +671,46 @@ var init_dist = __esm({
|
|
|
621
671
|
applied: z.array(teamGuidanceItemSchema).max(100),
|
|
622
672
|
omittedCount: z.number().int().nonnegative()
|
|
623
673
|
});
|
|
674
|
+
dependencyPackageSignalSchema = z.object({
|
|
675
|
+
name: z.string().min(1).max(160),
|
|
676
|
+
changeKind: z.enum([
|
|
677
|
+
"patch_or_minor",
|
|
678
|
+
"major",
|
|
679
|
+
"added_or_overridden",
|
|
680
|
+
"unknown"
|
|
681
|
+
]),
|
|
682
|
+
manifestPaths: z.array(z.string().min(1).max(500)).max(20),
|
|
683
|
+
lockfilePaths: z.array(z.string().min(1).max(500)).max(20),
|
|
684
|
+
evidence: z.array(z.string().min(1).max(120)).max(10)
|
|
685
|
+
});
|
|
686
|
+
dependencyRemediationSchema = z.object({
|
|
687
|
+
id: z.string().min(1).max(120),
|
|
688
|
+
status: z.enum(["ready", "needs_evidence"]),
|
|
689
|
+
packageManager: z.enum([
|
|
690
|
+
"pnpm",
|
|
691
|
+
"npm",
|
|
692
|
+
"yarn",
|
|
693
|
+
"bun",
|
|
694
|
+
"pip",
|
|
695
|
+
"bundler",
|
|
696
|
+
"go",
|
|
697
|
+
"cargo",
|
|
698
|
+
"unknown"
|
|
699
|
+
]),
|
|
700
|
+
manifestFiles: z.array(z.string().min(1).max(500)).max(30),
|
|
701
|
+
lockfiles: z.array(z.string().min(1).max(500)).max(20),
|
|
702
|
+
workspaceFiles: z.array(z.string().min(1).max(500)).max(20),
|
|
703
|
+
changedDependencyFiles: z.array(z.string().min(1).max(500)).max(30),
|
|
704
|
+
directDependencyFiles: z.array(z.string().min(1).max(500)).max(30),
|
|
705
|
+
packageSignals: z.array(dependencyPackageSignalSchema).max(20),
|
|
706
|
+
graphCommands: z.array(z.string().min(1).max(500)).max(20),
|
|
707
|
+
remediationCommands: z.array(z.string().min(1).max(500)).max(20),
|
|
708
|
+
validationCommands: z.array(z.string().min(1).max(500)).max(20),
|
|
709
|
+
residualRiskChecks: z.array(z.string().min(1).max(800)).max(20),
|
|
710
|
+
handoffRequirements: z.array(z.string().min(1).max(800)).max(20),
|
|
711
|
+
summary: z.string().max(1200),
|
|
712
|
+
confidence: z.number().min(0).max(100)
|
|
713
|
+
});
|
|
624
714
|
contextPacketSchema = z.object({
|
|
625
715
|
packetId: z.string().min(1),
|
|
626
716
|
createdAt: z.string().datetime(),
|
|
@@ -643,7 +733,8 @@ var init_dist = __esm({
|
|
|
643
733
|
appliedTeamGuidanceCount: z.number().int().nonnegative(),
|
|
644
734
|
suggestedTestCount: z.number().int().nonnegative()
|
|
645
735
|
}).optional(),
|
|
646
|
-
teamGuidance: teamGuidanceDeliverySchema.optional()
|
|
736
|
+
teamGuidance: teamGuidanceDeliverySchema.optional(),
|
|
737
|
+
dependencyRemediation: dependencyRemediationSchema.optional()
|
|
647
738
|
});
|
|
648
739
|
contextReceiptRequestSchema = z.object({
|
|
649
740
|
workspaceId: z.string().uuid().optional(),
|
|
@@ -674,6 +765,7 @@ var init_dist = __esm({
|
|
|
674
765
|
"small_refactor",
|
|
675
766
|
"bug_investigation",
|
|
676
767
|
"qa_edge_cases",
|
|
768
|
+
"web_dogfood",
|
|
677
769
|
"performance",
|
|
678
770
|
"porting",
|
|
679
771
|
"experiment",
|
|
@@ -5296,22 +5388,22 @@ function likelyRuleLine(line) {
|
|
|
5296
5388
|
return hasStrongRule || hasOperationalRule;
|
|
5297
5389
|
}
|
|
5298
5390
|
function scopeFromPath(sourcePath, kind) {
|
|
5299
|
-
const
|
|
5300
|
-
const directoryScope = scopedDirectory(
|
|
5301
|
-
if (directoryScope && (
|
|
5391
|
+
const path21 = sourcePath.replaceAll("\\", "/");
|
|
5392
|
+
const directoryScope = scopedDirectory(path21);
|
|
5393
|
+
if (directoryScope && (path21.endsWith("/AGENTS.md") || path21.endsWith("/CLAUDE.md")))
|
|
5302
5394
|
return [directoryScope];
|
|
5303
|
-
if (
|
|
5395
|
+
if (path21.includes("client/") || kind === "frontend")
|
|
5304
5396
|
return ["client/**", "packages/ui/**"];
|
|
5305
|
-
if (
|
|
5397
|
+
if (path21.includes("server/") || kind === "persistence")
|
|
5306
5398
|
return ["server/**"];
|
|
5307
|
-
if (
|
|
5399
|
+
if (path21.includes(".github/") || kind === "workflow")
|
|
5308
5400
|
return ["**/*"];
|
|
5309
|
-
if (
|
|
5401
|
+
if (path21.includes("test") || kind === "testing")
|
|
5310
5402
|
return ["**/*.test.*", "**/*.spec.*", "tests/**"];
|
|
5311
5403
|
return ["**/*"];
|
|
5312
5404
|
}
|
|
5313
|
-
function scopedDirectory(
|
|
5314
|
-
const parts =
|
|
5405
|
+
function scopedDirectory(path21) {
|
|
5406
|
+
const parts = path21.split("/");
|
|
5315
5407
|
if (parts.length <= 1)
|
|
5316
5408
|
return null;
|
|
5317
5409
|
const directory = parts.slice(0, -1).join("/");
|
|
@@ -5381,12 +5473,12 @@ function sourceReadLimit(type) {
|
|
|
5381
5473
|
return CODE_SOURCE_MAX_BYTES;
|
|
5382
5474
|
return void 0;
|
|
5383
5475
|
}
|
|
5384
|
-
function sourceDocument(
|
|
5476
|
+
function sourceDocument(pathName2, type, content) {
|
|
5385
5477
|
return {
|
|
5386
|
-
id: idFor("src", `${
|
|
5387
|
-
path:
|
|
5478
|
+
id: idFor("src", `${pathName2}:${content}`),
|
|
5479
|
+
path: pathName2,
|
|
5388
5480
|
type,
|
|
5389
|
-
title: titleFromPath(
|
|
5481
|
+
title: titleFromPath(pathName2),
|
|
5390
5482
|
excerpt: firstLines(content, 14),
|
|
5391
5483
|
content,
|
|
5392
5484
|
contentHash: contentHash(content),
|
|
@@ -7531,12 +7623,12 @@ function extractReviewMemories(sources, rules) {
|
|
|
7531
7623
|
}
|
|
7532
7624
|
return memories.slice(0, 32);
|
|
7533
7625
|
}
|
|
7534
|
-
function mapNode(type,
|
|
7626
|
+
function mapNode(type, pathName2, summary, tags, metadata) {
|
|
7535
7627
|
const node = {
|
|
7536
|
-
id: idFor("map", `${type}:${
|
|
7628
|
+
id: idFor("map", `${type}:${pathName2}`),
|
|
7537
7629
|
type,
|
|
7538
|
-
name: titleFromPath(
|
|
7539
|
-
path:
|
|
7630
|
+
name: titleFromPath(pathName2),
|
|
7631
|
+
path: pathName2,
|
|
7540
7632
|
summary,
|
|
7541
7633
|
tags
|
|
7542
7634
|
};
|
|
@@ -8605,7 +8697,7 @@ function agentPacketPrompt(input) {
|
|
|
8605
8697
|
].filter(Boolean).join(" ");
|
|
8606
8698
|
}
|
|
8607
8699
|
function inspectReasonForNode(node) {
|
|
8608
|
-
const
|
|
8700
|
+
const path21 = pathKey(node);
|
|
8609
8701
|
const systems = metadataStrings2(node, "systems");
|
|
8610
8702
|
const tables = metadataStrings2(node, "tables");
|
|
8611
8703
|
const access = metadataStrings2(node, "accessControls");
|
|
@@ -8625,7 +8717,7 @@ function inspectReasonForNode(node) {
|
|
|
8625
8717
|
return "Entrypoint that frames runtime flow.";
|
|
8626
8718
|
if (node.type === "component")
|
|
8627
8719
|
return "UI component surface in mapped scope.";
|
|
8628
|
-
return `Mapped ${node.type} node for ${
|
|
8720
|
+
return `Mapped ${node.type} node for ${path21}.`;
|
|
8629
8721
|
}
|
|
8630
8722
|
function strongestEntityEvidenceKind(nodes, kind, name) {
|
|
8631
8723
|
const matchingNodes = nodes.filter((node) => metadataStrings2(node, metadataKeyForEntity(kind)).includes(name));
|
|
@@ -8698,17 +8790,17 @@ function topFacet(nodes, key, limit) {
|
|
|
8698
8790
|
function topAreaFacet(nodes, limit) {
|
|
8699
8791
|
const areas = /* @__PURE__ */ new Map();
|
|
8700
8792
|
for (const node of nodes) {
|
|
8701
|
-
const
|
|
8702
|
-
const prefix = areaPrefix(
|
|
8793
|
+
const path21 = pathKey(node);
|
|
8794
|
+
const prefix = areaPrefix(path21);
|
|
8703
8795
|
const entry = areas.get(prefix) ?? {
|
|
8704
8796
|
paths: /* @__PURE__ */ new Set(),
|
|
8705
8797
|
codeSurfaceCount: 0,
|
|
8706
8798
|
systems: /* @__PURE__ */ new Set()
|
|
8707
8799
|
};
|
|
8708
|
-
if (!entry.paths.has(
|
|
8800
|
+
if (!entry.paths.has(path21) && CODE_SURFACE_TYPES.has(node.type)) {
|
|
8709
8801
|
entry.codeSurfaceCount += 1;
|
|
8710
8802
|
}
|
|
8711
|
-
entry.paths.add(
|
|
8803
|
+
entry.paths.add(path21);
|
|
8712
8804
|
for (const system of metadataStrings2(node, "systems")) {
|
|
8713
8805
|
entry.systems.add(system);
|
|
8714
8806
|
}
|
|
@@ -10564,7 +10656,7 @@ function wikiBuildPipeline(scan, pages, operationalAtlas, loopPlaybooks, refresh
|
|
|
10564
10656
|
},
|
|
10565
10657
|
{
|
|
10566
10658
|
id: "publish",
|
|
10567
|
-
title: "
|
|
10659
|
+
title: "Share with loops",
|
|
10568
10660
|
status: "ready",
|
|
10569
10661
|
summary: "Mission planning and context packets can cite wiki pages as source-grounded operating context.",
|
|
10570
10662
|
evidence: ["brief.wiki_outline", "brief.context_for_task"]
|
|
@@ -10992,7 +11084,7 @@ function loopPlaybookSpec(loopId) {
|
|
|
10992
11084
|
if (loopId === "session_library") {
|
|
10993
11085
|
return {
|
|
10994
11086
|
title: "Session library loop",
|
|
10995
|
-
summary: "Promote proven agent runs into reusable team skills, loops, styles, and run cards without
|
|
11087
|
+
summary: "Promote proven agent runs into reusable team skills, loops, styles, and run cards without sharing raw transcripts by default.",
|
|
10996
11088
|
tools: [
|
|
10997
11089
|
"brief.agent_session_insights",
|
|
10998
11090
|
"brief.value_report",
|
|
@@ -11013,7 +11105,7 @@ function loopPlaybookSpec(loopId) {
|
|
|
11013
11105
|
],
|
|
11014
11106
|
privacyBoundary: [
|
|
11015
11107
|
...common.privacyBoundary,
|
|
11016
|
-
"
|
|
11108
|
+
"Share concise run cards and styles, not raw private sessions, unless explicitly approved."
|
|
11017
11109
|
],
|
|
11018
11110
|
systemHints: ["github", "ci"],
|
|
11019
11111
|
queryHint: "agent session receipts run cards skills loops styles",
|
|
@@ -11204,7 +11296,7 @@ function wikiUsageReceipts(pages, options2) {
|
|
|
11204
11296
|
loopId: "session_library",
|
|
11205
11297
|
tool: "brief.mission_plan",
|
|
11206
11298
|
pageIds: pageIds("Session Library", "Review Memory", "Team Standards", "Maintainers"),
|
|
11207
|
-
reason: "Session library loops need approved examples, privacy decisions, maintainer context, and reusable team standards before
|
|
11299
|
+
reason: "Session library loops need approved examples, privacy decisions, maintainer context, and reusable team standards before sharing run cards or skills."
|
|
11208
11300
|
},
|
|
11209
11301
|
{
|
|
11210
11302
|
id: `wiki_use_${contentHash("docs:overview:structure")}`,
|
|
@@ -11387,9 +11479,9 @@ var init_contextWiki = __esm({
|
|
|
11387
11479
|
});
|
|
11388
11480
|
|
|
11389
11481
|
// ../core/dist/pathIntent.js
|
|
11390
|
-
function namedCodePathIntentScore(
|
|
11482
|
+
function namedCodePathIntentScore(path21, task) {
|
|
11391
11483
|
const normalizedTask = task.toLowerCase().replace(/[_-]+/g, " ");
|
|
11392
|
-
const normalizedPath = (
|
|
11484
|
+
const normalizedPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
|
|
11393
11485
|
let score = 0;
|
|
11394
11486
|
const namedTargets = [
|
|
11395
11487
|
[/\breview diff\b/, /diffreview|diff[-_]review/],
|
|
@@ -11535,9 +11627,476 @@ var init_pathIntent = __esm({
|
|
|
11535
11627
|
}
|
|
11536
11628
|
});
|
|
11537
11629
|
|
|
11630
|
+
// ../core/dist/dependencyRemediation.js
|
|
11631
|
+
import path6 from "node:path";
|
|
11632
|
+
function buildDependencyRemediationPlan(repoRoot, request) {
|
|
11633
|
+
const files = walkFiles(repoRoot, request.maxFiles ?? 6e3);
|
|
11634
|
+
const manifests = files.filter(isDependencyManifest);
|
|
11635
|
+
const lockfiles = files.filter(isDependencyLockfile);
|
|
11636
|
+
const workspaceFiles = files.filter(isDependencyWorkspaceFile);
|
|
11637
|
+
const changedDependencyFiles = unique((request.changedFiles ?? []).filter(isDependencyFile));
|
|
11638
|
+
const packageManager = inferPackageManager(repoRoot, manifests, lockfiles);
|
|
11639
|
+
const packageSignalInput = {
|
|
11640
|
+
task: request.task,
|
|
11641
|
+
manifests,
|
|
11642
|
+
changedDependencyFiles
|
|
11643
|
+
};
|
|
11644
|
+
if (request.diff)
|
|
11645
|
+
packageSignalInput.diff = request.diff;
|
|
11646
|
+
const packageSignals = packageSignalsFor(repoRoot, packageSignalInput);
|
|
11647
|
+
const directDependencyFiles = unique(packageSignals.flatMap((signal2) => signal2.manifestPaths));
|
|
11648
|
+
const packageNames = packageSignals.map((signal2) => signal2.name);
|
|
11649
|
+
const validationCommands2 = validationCommandsFor2(repoRoot, packageManager);
|
|
11650
|
+
const graphCommands = dependencyGraphCommandsFor(packageManager, packageNames);
|
|
11651
|
+
const remediationCommands = remediationCommandsFor(packageManager, packageNames);
|
|
11652
|
+
const residualRiskChecks = residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles);
|
|
11653
|
+
const confidence = confidenceFor({
|
|
11654
|
+
changedDependencyFiles,
|
|
11655
|
+
packageSignals,
|
|
11656
|
+
lockfiles,
|
|
11657
|
+
validationCommands: validationCommands2
|
|
11658
|
+
});
|
|
11659
|
+
return {
|
|
11660
|
+
id: `dep_${contentHash([
|
|
11661
|
+
request.task,
|
|
11662
|
+
packageManager,
|
|
11663
|
+
changedDependencyFiles.join(","),
|
|
11664
|
+
packageNames.join(",")
|
|
11665
|
+
].join(":"))}`,
|
|
11666
|
+
status: changedDependencyFiles.length > 0 || packageSignals.length > 0 ? "ready" : "needs_evidence",
|
|
11667
|
+
packageManager,
|
|
11668
|
+
manifestFiles: manifests.slice(0, 20),
|
|
11669
|
+
lockfiles,
|
|
11670
|
+
workspaceFiles,
|
|
11671
|
+
changedDependencyFiles,
|
|
11672
|
+
directDependencyFiles,
|
|
11673
|
+
packageSignals,
|
|
11674
|
+
graphCommands,
|
|
11675
|
+
remediationCommands,
|
|
11676
|
+
validationCommands: validationCommands2,
|
|
11677
|
+
residualRiskChecks,
|
|
11678
|
+
handoffRequirements: [
|
|
11679
|
+
"List advisory ids or scanner finding ids that were fixed.",
|
|
11680
|
+
"Name packages still present in the lockfile with vulnerable versions, or state that the scan/audit is clean.",
|
|
11681
|
+
"Explain any major-version upgrades, overrides, peer dependency changes, skipped advisories, or remaining advisory ids.",
|
|
11682
|
+
"Attach validation commands and CI follow-up results before marking the security mission done."
|
|
11683
|
+
],
|
|
11684
|
+
summary: summaryFor3({
|
|
11685
|
+
packageManager,
|
|
11686
|
+
manifests,
|
|
11687
|
+
lockfiles,
|
|
11688
|
+
changedDependencyFiles,
|
|
11689
|
+
packageSignals,
|
|
11690
|
+
validationCommands: validationCommands2
|
|
11691
|
+
}),
|
|
11692
|
+
confidence
|
|
11693
|
+
};
|
|
11694
|
+
}
|
|
11695
|
+
function isDependencyRemediationTask(task, files = []) {
|
|
11696
|
+
const normalized = task.toLowerCase();
|
|
11697
|
+
const hasSecuritySignal = /\b(dependabot|vulnerab\w*|cve-|ghsa-|security advisory|security finding|audit finding|osv|sca|dependency-review)\b/.test(normalized);
|
|
11698
|
+
const hasDependencyChangeSignal = /\b(dependency|dependencies|lockfile|package upgrade|package update|package bump|transitive|override|resolution)\b/.test(normalized);
|
|
11699
|
+
return hasSecuritySignal || hasDependencyChangeSignal && files.some(isDependencyFile);
|
|
11700
|
+
}
|
|
11701
|
+
function isDependencyFile(filePath) {
|
|
11702
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
11703
|
+
return dependencyFilePatterns.some((pattern) => normalized === pattern || normalized.endsWith(`/${pattern}`) || pattern === "package.json" && normalized.endsWith("/package.json"));
|
|
11704
|
+
}
|
|
11705
|
+
function isDependencyLockfile(filePath) {
|
|
11706
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
11707
|
+
return normalized === "pnpm-lock.yaml" || normalized.endsWith("/pnpm-lock.yaml") || normalized === "package-lock.json" || normalized.endsWith("/package-lock.json") || normalized === "yarn.lock" || normalized.endsWith("/yarn.lock") || normalized === "bun.lock" || normalized.endsWith("/bun.lock") || normalized === "npm-shrinkwrap.json" || normalized.endsWith("/npm-shrinkwrap.json") || normalized === "poetry.lock" || normalized.endsWith("/poetry.lock") || normalized === "Pipfile.lock" || normalized.endsWith("/Pipfile.lock") || normalized === "Gemfile.lock" || normalized.endsWith("/Gemfile.lock") || normalized === "go.sum" || normalized.endsWith("/go.sum") || normalized === "Cargo.lock" || normalized.endsWith("/Cargo.lock");
|
|
11708
|
+
}
|
|
11709
|
+
function isDependencyManifest(filePath) {
|
|
11710
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
11711
|
+
return normalized === "package.json" || normalized.endsWith("/package.json") || normalized === "requirements.txt" || normalized.endsWith("/requirements.txt") || normalized === "pyproject.toml" || normalized.endsWith("/pyproject.toml") || normalized === "Gemfile" || normalized.endsWith("/Gemfile") || normalized === "go.mod" || normalized.endsWith("/go.mod") || normalized === "Cargo.toml" || normalized.endsWith("/Cargo.toml");
|
|
11712
|
+
}
|
|
11713
|
+
function isDependencyWorkspaceFile(filePath) {
|
|
11714
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
11715
|
+
return normalized === "pnpm-workspace.yaml" || normalized.endsWith("/pnpm-workspace.yaml") || normalized === "lerna.json" || normalized.endsWith("/lerna.json") || normalized === "turbo.json" || normalized.endsWith("/turbo.json") || normalized === "nx.json" || normalized.endsWith("/nx.json");
|
|
11716
|
+
}
|
|
11717
|
+
function inferPackageManager(repoRoot, manifests, lockfiles) {
|
|
11718
|
+
const rootPackageJson = readPackageJson(repoRoot, "package.json");
|
|
11719
|
+
const packageManager = rootPackageJson?.packageManager;
|
|
11720
|
+
if (typeof packageManager === "string") {
|
|
11721
|
+
if (packageManager.startsWith("pnpm@"))
|
|
11722
|
+
return "pnpm";
|
|
11723
|
+
if (packageManager.startsWith("yarn@"))
|
|
11724
|
+
return "yarn";
|
|
11725
|
+
if (packageManager.startsWith("npm@"))
|
|
11726
|
+
return "npm";
|
|
11727
|
+
if (packageManager.startsWith("bun@"))
|
|
11728
|
+
return "bun";
|
|
11729
|
+
}
|
|
11730
|
+
if (lockfiles.some((file) => file.endsWith("pnpm-lock.yaml")))
|
|
11731
|
+
return "pnpm";
|
|
11732
|
+
if (lockfiles.some((file) => file.endsWith("yarn.lock")))
|
|
11733
|
+
return "yarn";
|
|
11734
|
+
if (lockfiles.some((file) => file.endsWith("package-lock.json"))) {
|
|
11735
|
+
return "npm";
|
|
11736
|
+
}
|
|
11737
|
+
if (lockfiles.some((file) => file.endsWith("bun.lock")))
|
|
11738
|
+
return "bun";
|
|
11739
|
+
if (manifests.some((file) => file.endsWith("requirements.txt") || file.endsWith("pyproject.toml") || file.endsWith("Pipfile"))) {
|
|
11740
|
+
return "pip";
|
|
11741
|
+
}
|
|
11742
|
+
if (manifests.some((file) => file.endsWith("Gemfile")))
|
|
11743
|
+
return "bundler";
|
|
11744
|
+
if (manifests.some((file) => file.endsWith("go.mod")))
|
|
11745
|
+
return "go";
|
|
11746
|
+
if (manifests.some((file) => file.endsWith("Cargo.toml")))
|
|
11747
|
+
return "cargo";
|
|
11748
|
+
return "unknown";
|
|
11749
|
+
}
|
|
11750
|
+
function packageSignalsFor(repoRoot, input) {
|
|
11751
|
+
const manifestDeps = dependencyNamesByManifest(repoRoot, input.manifests);
|
|
11752
|
+
const explicitPackageNames = unique([
|
|
11753
|
+
...packagesMentionedInText(input.task),
|
|
11754
|
+
...packagesMentionedInDiff(input.diff ?? "")
|
|
11755
|
+
]).filter((name) => !looksLikeVersionOrAdvisory(name));
|
|
11756
|
+
const fallbackPackageNames = input.changedDependencyFiles.flatMap((file) => packagesFromChangedDependencyFile(repoRoot, file));
|
|
11757
|
+
const packageNames = unique(explicitPackageNames.length > 0 ? explicitPackageNames : fallbackPackageNames).filter((name) => !looksLikeVersionOrAdvisory(name));
|
|
11758
|
+
return packageNames.slice(0, 16).map((name) => ({
|
|
11759
|
+
name,
|
|
11760
|
+
changeKind: changeKindFor(name, input.diff ?? ""),
|
|
11761
|
+
manifestPaths: manifestsForPackage(name, manifestDeps),
|
|
11762
|
+
lockfilePaths: input.changedDependencyFiles.filter(isDependencyLockfile),
|
|
11763
|
+
evidence: evidenceForPackage(name, input.task, input.diff ?? "")
|
|
11764
|
+
}));
|
|
11765
|
+
}
|
|
11766
|
+
function dependencyNamesByManifest(repoRoot, manifests) {
|
|
11767
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
11768
|
+
for (const manifest of manifests) {
|
|
11769
|
+
if (!manifest.endsWith("package.json"))
|
|
11770
|
+
continue;
|
|
11771
|
+
const parsed = readPackageJson(repoRoot, manifest);
|
|
11772
|
+
if (!parsed)
|
|
11773
|
+
continue;
|
|
11774
|
+
for (const name of packageJsonDependencyNames(parsed)) {
|
|
11775
|
+
const current = byPackage.get(name) ?? [];
|
|
11776
|
+
current.push(manifest);
|
|
11777
|
+
byPackage.set(name, current);
|
|
11778
|
+
}
|
|
11779
|
+
}
|
|
11780
|
+
return byPackage;
|
|
11781
|
+
}
|
|
11782
|
+
function packageJsonDependencyNames(parsed) {
|
|
11783
|
+
return unique([
|
|
11784
|
+
"dependencies",
|
|
11785
|
+
"devDependencies",
|
|
11786
|
+
"optionalDependencies",
|
|
11787
|
+
"peerDependencies",
|
|
11788
|
+
"overrides",
|
|
11789
|
+
"resolutions",
|
|
11790
|
+
"pnpm"
|
|
11791
|
+
].flatMap((field) => nestedDependencyNames(parsed[field])));
|
|
11792
|
+
}
|
|
11793
|
+
function nestedDependencyNames(value) {
|
|
11794
|
+
if (!value || typeof value !== "object")
|
|
11795
|
+
return [];
|
|
11796
|
+
return Object.entries(value).flatMap(([key, nested]) => [
|
|
11797
|
+
looksLikePackageName(key) ? key : "",
|
|
11798
|
+
...nestedDependencyNames(nested)
|
|
11799
|
+
]);
|
|
11800
|
+
}
|
|
11801
|
+
function packagesMentionedInText(text) {
|
|
11802
|
+
const names = [];
|
|
11803
|
+
const quoted = text.matchAll(/[`'"](@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)[`'"]/gi);
|
|
11804
|
+
for (const match of quoted)
|
|
11805
|
+
names.push(match[1] ?? "");
|
|
11806
|
+
const advisoryContext = text.matchAll(/\b(?:package|dependency|upgrade|update|override|transitive|vulnerable|dependabot|cve|ghsa|audit)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
|
|
11807
|
+
for (const match of advisoryContext)
|
|
11808
|
+
names.push(match[1] ?? "");
|
|
11809
|
+
const atVersion = text.matchAll(/\b(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/gi);
|
|
11810
|
+
for (const match of atVersion)
|
|
11811
|
+
names.push(match[1] ?? "");
|
|
11812
|
+
if (/\b(dependabot|cve-|ghsa-|audit|vulnerab|advisory)\b/i.test(text)) {
|
|
11813
|
+
const affectedPackageContext = text.matchAll(/\b(?:for|affects?|in)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
|
|
11814
|
+
for (const match of affectedPackageContext)
|
|
11815
|
+
names.push(match[1] ?? "");
|
|
11816
|
+
}
|
|
11817
|
+
return unique(names.map(cleanPackageName).filter(looksLikePackageName).filter((name) => !genericDependencyWords.has(name.toLowerCase())));
|
|
11818
|
+
}
|
|
11819
|
+
function packagesMentionedInDiff(diff) {
|
|
11820
|
+
const names = [];
|
|
11821
|
+
for (const line of diff.split(/\r?\n/)) {
|
|
11822
|
+
if (!/^[+-]/.test(line) || /^[+-]{3}/.test(line))
|
|
11823
|
+
continue;
|
|
11824
|
+
const packageJsonMatch = line.match(/"(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)"\s*:/i);
|
|
11825
|
+
if (packageJsonMatch?.[1])
|
|
11826
|
+
names.push(packageJsonMatch[1]);
|
|
11827
|
+
const pnpmMatch = line.match(/^\s*[+-]\s{0,8}(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
|
|
11828
|
+
if (pnpmMatch?.[1])
|
|
11829
|
+
names.push(pnpmMatch[1]);
|
|
11830
|
+
const lockPathMatch = line.match(/\/(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
|
|
11831
|
+
if (lockPathMatch?.[1])
|
|
11832
|
+
names.push(lockPathMatch[1]);
|
|
11833
|
+
}
|
|
11834
|
+
return unique(names.map(cleanPackageName).filter(looksLikePackageName).filter((name) => !genericDependencyWords.has(name.toLowerCase())));
|
|
11835
|
+
}
|
|
11836
|
+
function packagesFromChangedDependencyFile(repoRoot, filePath) {
|
|
11837
|
+
if (!filePath.endsWith("package.json"))
|
|
11838
|
+
return [];
|
|
11839
|
+
const parsed = readPackageJson(repoRoot, filePath);
|
|
11840
|
+
return parsed ? packageJsonDependencyNames(parsed).slice(0, 20) : [];
|
|
11841
|
+
}
|
|
11842
|
+
function readPackageJson(repoRoot, filePath) {
|
|
11843
|
+
const content = safeRead(path6.join(repoRoot, filePath), 4e5);
|
|
11844
|
+
if (!content)
|
|
11845
|
+
return null;
|
|
11846
|
+
try {
|
|
11847
|
+
const parsed = JSON.parse(content);
|
|
11848
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
11849
|
+
} catch {
|
|
11850
|
+
return null;
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11853
|
+
function changeKindFor(packageName, diff) {
|
|
11854
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11855
|
+
const removedVersions = [
|
|
11856
|
+
...diff.matchAll(new RegExp(`^-.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
|
|
11857
|
+
].map((match) => match[1] ?? "");
|
|
11858
|
+
const addedVersions = [
|
|
11859
|
+
...diff.matchAll(new RegExp(`^\\+.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
|
|
11860
|
+
].map((match) => match[1] ?? "");
|
|
11861
|
+
const removed = removedVersions[0];
|
|
11862
|
+
const added = addedVersions[0];
|
|
11863
|
+
if (removed && added) {
|
|
11864
|
+
const [removedMajor] = removed.split(".");
|
|
11865
|
+
const [addedMajor] = added.split(".");
|
|
11866
|
+
return removedMajor && addedMajor && removedMajor !== addedMajor ? "major" : "patch_or_minor";
|
|
11867
|
+
}
|
|
11868
|
+
if (added && !removed)
|
|
11869
|
+
return "added_or_overridden";
|
|
11870
|
+
return "unknown";
|
|
11871
|
+
}
|
|
11872
|
+
function manifestsForPackage(packageName, manifestDeps) {
|
|
11873
|
+
return unique([
|
|
11874
|
+
...manifestDeps.get(packageName) ?? [],
|
|
11875
|
+
...[...manifestDeps.entries()].filter(([name]) => name.endsWith(`/${packageName}`)).flatMap(([, manifests]) => manifests)
|
|
11876
|
+
]).slice(0, 8);
|
|
11877
|
+
}
|
|
11878
|
+
function evidenceForPackage(packageName, task, diff) {
|
|
11879
|
+
const evidence = [];
|
|
11880
|
+
if (task.toLowerCase().includes(packageName.toLowerCase())) {
|
|
11881
|
+
evidence.push("mentioned_in_task");
|
|
11882
|
+
}
|
|
11883
|
+
if (diff.toLowerCase().includes(packageName.toLowerCase())) {
|
|
11884
|
+
evidence.push("mentioned_in_diff");
|
|
11885
|
+
}
|
|
11886
|
+
if (evidence.length === 0)
|
|
11887
|
+
evidence.push("manifest_candidate");
|
|
11888
|
+
return evidence;
|
|
11889
|
+
}
|
|
11890
|
+
function validationCommandsFor2(repoRoot, packageManager) {
|
|
11891
|
+
const scripts = packageScripts(repoRoot);
|
|
11892
|
+
const run = packageRunCommand(packageManager);
|
|
11893
|
+
return unique([
|
|
11894
|
+
...auditCommandsFor(packageManager),
|
|
11895
|
+
...scriptCommand(scripts, "check", run),
|
|
11896
|
+
...scriptCommand(scripts, "test", run),
|
|
11897
|
+
...scriptCommand(scripts, "test:client", run),
|
|
11898
|
+
...scriptCommand(scripts, "test:e2e:smoke", run),
|
|
11899
|
+
...scriptCommand(scripts, "build", run)
|
|
11900
|
+
]).slice(0, 10);
|
|
11901
|
+
}
|
|
11902
|
+
function packageScripts(repoRoot) {
|
|
11903
|
+
const parsed = readPackageJson(repoRoot, "package.json");
|
|
11904
|
+
const scripts = parsed?.scripts;
|
|
11905
|
+
return scripts && typeof scripts === "object" ? Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string")) : {};
|
|
11906
|
+
}
|
|
11907
|
+
function scriptCommand(scripts, scriptName, runCommand) {
|
|
11908
|
+
return scripts[scriptName] ? [`${runCommand} ${scriptName}`] : [];
|
|
11909
|
+
}
|
|
11910
|
+
function packageRunCommand(packageManager) {
|
|
11911
|
+
switch (packageManager) {
|
|
11912
|
+
case "npm":
|
|
11913
|
+
return "npm run";
|
|
11914
|
+
case "yarn":
|
|
11915
|
+
return "yarn";
|
|
11916
|
+
case "bun":
|
|
11917
|
+
return "bun run";
|
|
11918
|
+
default:
|
|
11919
|
+
return "pnpm";
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11922
|
+
function auditCommandsFor(packageManager) {
|
|
11923
|
+
switch (packageManager) {
|
|
11924
|
+
case "pnpm":
|
|
11925
|
+
return ["pnpm audit --prod"];
|
|
11926
|
+
case "npm":
|
|
11927
|
+
return ["npm audit --omit=dev"];
|
|
11928
|
+
case "yarn":
|
|
11929
|
+
return ["yarn npm audit --environment production"];
|
|
11930
|
+
case "bun":
|
|
11931
|
+
return ["bun audit"];
|
|
11932
|
+
case "pip":
|
|
11933
|
+
return ["pip-audit"];
|
|
11934
|
+
case "bundler":
|
|
11935
|
+
return ["bundle audit check"];
|
|
11936
|
+
case "go":
|
|
11937
|
+
return ["govulncheck ./..."];
|
|
11938
|
+
case "cargo":
|
|
11939
|
+
return ["cargo audit"];
|
|
11940
|
+
default:
|
|
11941
|
+
return [];
|
|
11942
|
+
}
|
|
11943
|
+
}
|
|
11944
|
+
function dependencyGraphCommandsFor(packageManager, packageNames) {
|
|
11945
|
+
const selected = packageNames.slice(0, 5);
|
|
11946
|
+
if (selected.length === 0)
|
|
11947
|
+
return [];
|
|
11948
|
+
switch (packageManager) {
|
|
11949
|
+
case "pnpm":
|
|
11950
|
+
return selected.map((name) => `pnpm why ${name}`);
|
|
11951
|
+
case "npm":
|
|
11952
|
+
return selected.map((name) => `npm explain ${name}`);
|
|
11953
|
+
case "yarn":
|
|
11954
|
+
return selected.map((name) => `yarn why ${name}`);
|
|
11955
|
+
case "bun":
|
|
11956
|
+
return selected.map((name) => `bun pm why ${name}`);
|
|
11957
|
+
case "go":
|
|
11958
|
+
return selected.map((name) => `go mod why -m ${name}`);
|
|
11959
|
+
case "cargo":
|
|
11960
|
+
return selected.map((name) => `cargo tree -i ${name}`);
|
|
11961
|
+
default:
|
|
11962
|
+
return [];
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
function remediationCommandsFor(packageManager, packageNames) {
|
|
11966
|
+
const selected = packageNames.slice(0, 5);
|
|
11967
|
+
if (selected.length === 0)
|
|
11968
|
+
return [];
|
|
11969
|
+
switch (packageManager) {
|
|
11970
|
+
case "pnpm":
|
|
11971
|
+
return selected.map((name) => `pnpm up ${name} --latest --interactive=false`);
|
|
11972
|
+
case "npm":
|
|
11973
|
+
return selected.map((name) => `npm update ${name}`);
|
|
11974
|
+
case "yarn":
|
|
11975
|
+
return selected.map((name) => `yarn up ${name}`);
|
|
11976
|
+
case "bun":
|
|
11977
|
+
return selected.map((name) => `bun update ${name}`);
|
|
11978
|
+
case "go":
|
|
11979
|
+
return selected.map((name) => `go get ${name}@latest`);
|
|
11980
|
+
case "cargo":
|
|
11981
|
+
return selected.map((name) => `cargo update -p ${name}`);
|
|
11982
|
+
default:
|
|
11983
|
+
return [];
|
|
11984
|
+
}
|
|
11985
|
+
}
|
|
11986
|
+
function residualRiskChecksFor(packageManager, packageNames, changedDependencyFiles) {
|
|
11987
|
+
return unique([
|
|
11988
|
+
packageNames.length > 0 ? `Confirm vulnerable versions are gone for: ${packageNames.slice(0, 8).join(", ")}.` : "Name each advisory/package from the scanner before editing.",
|
|
11989
|
+
changedDependencyFiles.some(isDependencyLockfile) ? "Inspect the lockfile diff for unexpected package churn outside the advisory path." : "Regenerate and inspect the lockfile if the fix changes dependency resolution.",
|
|
11990
|
+
"Split major-version upgrades or broad framework upgrades into a separate mission unless the advisory requires them.",
|
|
11991
|
+
"Record any remaining advisory ids, ignored dev-only findings, or transitive paths that still need owner judgment.",
|
|
11992
|
+
...auditCommandsFor(packageManager).map((command2) => `Rerun ${command2} after the lockfile update.`)
|
|
11993
|
+
]);
|
|
11994
|
+
}
|
|
11995
|
+
function confidenceFor(input) {
|
|
11996
|
+
let score = 35;
|
|
11997
|
+
if (input.changedDependencyFiles.length > 0)
|
|
11998
|
+
score += 20;
|
|
11999
|
+
if (input.packageSignals.length > 0)
|
|
12000
|
+
score += 20;
|
|
12001
|
+
if (input.lockfiles.length > 0)
|
|
12002
|
+
score += 10;
|
|
12003
|
+
if (input.validationCommands.length > 0)
|
|
12004
|
+
score += 10;
|
|
12005
|
+
return Math.min(95, score);
|
|
12006
|
+
}
|
|
12007
|
+
function summaryFor3(input) {
|
|
12008
|
+
const packages = input.packageSignals.map((signal2) => signal2.name);
|
|
12009
|
+
return [
|
|
12010
|
+
`Detected ${input.packageManager} dependency remediation context.`,
|
|
12011
|
+
`${input.manifests.length} manifest${input.manifests.length === 1 ? "" : "s"}, ${input.lockfiles.length} lockfile${input.lockfiles.length === 1 ? "" : "s"}.`,
|
|
12012
|
+
input.changedDependencyFiles.length > 0 ? `Changed dependency files: ${input.changedDependencyFiles.slice(0, 6).join(", ")}.` : "No dependency files are changed yet.",
|
|
12013
|
+
packages.length > 0 ? `Likely packages: ${packages.slice(0, 8).join(", ")}.` : "Package names still need advisory/scanner evidence.",
|
|
12014
|
+
input.validationCommands.length > 0 ? `Validation starts with ${input.validationCommands[0]}.` : "Validation commands need repo-specific confirmation."
|
|
12015
|
+
].join(" ");
|
|
12016
|
+
}
|
|
12017
|
+
function cleanPackageName(input) {
|
|
12018
|
+
return input.replace(/[),.;\]]+$/g, "").trim();
|
|
12019
|
+
}
|
|
12020
|
+
function looksLikePackageName(input) {
|
|
12021
|
+
return /^@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?$/i.test(input);
|
|
12022
|
+
}
|
|
12023
|
+
function looksLikeVersionOrAdvisory(input) {
|
|
12024
|
+
const normalized = input.toLowerCase();
|
|
12025
|
+
return /^\d/.test(input) || /^cve-\d/i.test(input) || /^ghsa-/i.test(input) || [
|
|
12026
|
+
"advisory",
|
|
12027
|
+
"advisories",
|
|
12028
|
+
"change",
|
|
12029
|
+
"changes",
|
|
12030
|
+
"dependency",
|
|
12031
|
+
"dependencies",
|
|
12032
|
+
"dependabot",
|
|
12033
|
+
"finding",
|
|
12034
|
+
"findings",
|
|
12035
|
+
"package",
|
|
12036
|
+
"packages",
|
|
12037
|
+
"security",
|
|
12038
|
+
"transitive",
|
|
12039
|
+
"upgrade",
|
|
12040
|
+
"upgrades",
|
|
12041
|
+
"vulnerabilities",
|
|
12042
|
+
"vulnerability",
|
|
12043
|
+
"vulnerable"
|
|
12044
|
+
].includes(normalized);
|
|
12045
|
+
}
|
|
12046
|
+
var dependencyFilePatterns, genericDependencyWords;
|
|
12047
|
+
var init_dependencyRemediation = __esm({
|
|
12048
|
+
"../core/dist/dependencyRemediation.js"() {
|
|
12049
|
+
"use strict";
|
|
12050
|
+
init_utils();
|
|
12051
|
+
dependencyFilePatterns = [
|
|
12052
|
+
"package.json",
|
|
12053
|
+
"pnpm-lock.yaml",
|
|
12054
|
+
"pnpm-workspace.yaml",
|
|
12055
|
+
"package-lock.json",
|
|
12056
|
+
"yarn.lock",
|
|
12057
|
+
"bun.lock",
|
|
12058
|
+
"npm-shrinkwrap.json",
|
|
12059
|
+
"requirements.txt",
|
|
12060
|
+
"poetry.lock",
|
|
12061
|
+
"pyproject.toml",
|
|
12062
|
+
"Pipfile.lock",
|
|
12063
|
+
"Gemfile",
|
|
12064
|
+
"Gemfile.lock",
|
|
12065
|
+
"go.mod",
|
|
12066
|
+
"go.sum",
|
|
12067
|
+
"Cargo.toml",
|
|
12068
|
+
"Cargo.lock"
|
|
12069
|
+
];
|
|
12070
|
+
genericDependencyWords = /* @__PURE__ */ new Set([
|
|
12071
|
+
"advisory",
|
|
12072
|
+
"audit",
|
|
12073
|
+
"build",
|
|
12074
|
+
"change",
|
|
12075
|
+
"dependency",
|
|
12076
|
+
"dev",
|
|
12077
|
+
"exact",
|
|
12078
|
+
"existing",
|
|
12079
|
+
"focused",
|
|
12080
|
+
"for",
|
|
12081
|
+
"finding",
|
|
12082
|
+
"lockfile",
|
|
12083
|
+
"package",
|
|
12084
|
+
"path",
|
|
12085
|
+
"security",
|
|
12086
|
+
"test",
|
|
12087
|
+
"the",
|
|
12088
|
+
"toolchain",
|
|
12089
|
+
"transitive",
|
|
12090
|
+
"vulnerability",
|
|
12091
|
+
"workflow",
|
|
12092
|
+
"without"
|
|
12093
|
+
]);
|
|
12094
|
+
}
|
|
12095
|
+
});
|
|
12096
|
+
|
|
11538
12097
|
// ../core/dist/testRecommendations.js
|
|
11539
12098
|
import { existsSync as existsSync3 } from "node:fs";
|
|
11540
|
-
import
|
|
12099
|
+
import path7 from "node:path";
|
|
11541
12100
|
function recommendExactTestsForFiles(input) {
|
|
11542
12101
|
const packageManager = detectPackageManager(input.repoRoot);
|
|
11543
12102
|
if (isDocsOnlyChange(input.changedFiles)) {
|
|
@@ -11691,7 +12250,7 @@ function taskRelevantTests(repoRoot, scanMap, scanSources, task, excluded) {
|
|
|
11691
12250
|
}));
|
|
11692
12251
|
return [...mapTests, ...sourceTests].filter((test) => !excludedSet.has(test.path)).map((test) => {
|
|
11693
12252
|
const pathScore = tokens.reduce((total, token) => total + (test.seed.includes(token) ? 2 : 0), 0);
|
|
11694
|
-
const content = pathScore > 0 ? (safeRead(
|
|
12253
|
+
const content = pathScore > 0 ? (safeRead(path7.join(repoRoot, test.path), 6e4) ?? "").toLowerCase() : "";
|
|
11695
12254
|
return {
|
|
11696
12255
|
path: test.path,
|
|
11697
12256
|
score: pathScore + tokens.reduce((total, token) => total + (test.context.includes(token) ? 1 : 0), 0) + tokens.reduce((total, token) => total + (content.includes(token) ? 1 : 0), 0)
|
|
@@ -11755,8 +12314,8 @@ function testCommandForFile(repoRoot, file, packageManager) {
|
|
|
11755
12314
|
if (packageManager === "pnpm") {
|
|
11756
12315
|
const packageInfo = nearestPackageInfo(repoRoot, file);
|
|
11757
12316
|
if (packageInfo?.name && packageInfo.root !== repoRoot) {
|
|
11758
|
-
const packageRelativeFile =
|
|
11759
|
-
const testScript =
|
|
12317
|
+
const packageRelativeFile = path7.relative(packageInfo.root, path7.join(repoRoot, file)).replaceAll("\\", "/");
|
|
12318
|
+
const testScript = readPackageJson2(packageInfo.root)?.scripts?.test;
|
|
11760
12319
|
if (typeof testScript === "string") {
|
|
11761
12320
|
if (/\bvitest\b/.test(testScript)) {
|
|
11762
12321
|
return `pnpm --filter ${packageInfo.name} exec vitest run ${packageRelativeFile}`;
|
|
@@ -11774,11 +12333,11 @@ function testCommandForFile(repoRoot, file, packageManager) {
|
|
|
11774
12333
|
return `${packageManager} test -- ${file}`;
|
|
11775
12334
|
}
|
|
11776
12335
|
function nearestPackageInfo(repoRoot, file) {
|
|
11777
|
-
let current =
|
|
12336
|
+
let current = path7.dirname(path7.join(repoRoot, file));
|
|
11778
12337
|
while (current.startsWith(repoRoot)) {
|
|
11779
|
-
const packageJsonPath =
|
|
12338
|
+
const packageJsonPath = path7.join(current, "package.json");
|
|
11780
12339
|
if (existsSync3(packageJsonPath)) {
|
|
11781
|
-
const pkg =
|
|
12340
|
+
const pkg = readPackageJson2(current);
|
|
11782
12341
|
return {
|
|
11783
12342
|
root: current,
|
|
11784
12343
|
...typeof pkg?.name === "string" ? { name: pkg.name } : {}
|
|
@@ -11786,18 +12345,18 @@ function nearestPackageInfo(repoRoot, file) {
|
|
|
11786
12345
|
}
|
|
11787
12346
|
if (current === repoRoot)
|
|
11788
12347
|
break;
|
|
11789
|
-
current =
|
|
12348
|
+
current = path7.dirname(current);
|
|
11790
12349
|
}
|
|
11791
12350
|
return null;
|
|
11792
12351
|
}
|
|
11793
12352
|
function coLocatedTestCandidates(repoRoot, file) {
|
|
11794
|
-
const parsed =
|
|
12353
|
+
const parsed = path7.parse(file);
|
|
11795
12354
|
const extensions = parsed.ext === ".tsx" ? [".test.tsx", ".spec.tsx", ".test.ts", ".spec.ts"] : parsed.ext === ".jsx" ? [".test.jsx", ".spec.jsx", ".test.js", ".spec.js"] : [`.test${parsed.ext}`, `.spec${parsed.ext}`];
|
|
11796
12355
|
const candidates = [
|
|
11797
|
-
...extensions.map((extension) =>
|
|
11798
|
-
...extensions.map((extension) =>
|
|
12356
|
+
...extensions.map((extension) => path7.join(parsed.dir, `${parsed.name}${extension}`)),
|
|
12357
|
+
...extensions.map((extension) => path7.join(parsed.dir, "__tests__", `${parsed.name}${extension}`))
|
|
11799
12358
|
].map((candidate) => candidate.replaceAll("\\", "/"));
|
|
11800
|
-
return candidates.filter((candidate) => existsSync3(
|
|
12359
|
+
return candidates.filter((candidate) => existsSync3(path7.join(repoRoot, candidate)));
|
|
11801
12360
|
}
|
|
11802
12361
|
function importedByTests(scanMap, sourceFiles) {
|
|
11803
12362
|
const fromSourceMetadata = scanMap.filter((node) => sourceFiles.includes(sourcePathForNode(node))).flatMap((node) => metadataStrings3(node, "importedBy")).filter(isTestPath);
|
|
@@ -11819,11 +12378,11 @@ function isDocsOnlyChange(files) {
|
|
|
11819
12378
|
}
|
|
11820
12379
|
function isDocsOrProductDocPath(file) {
|
|
11821
12380
|
const normalized = file.replaceAll("\\", "/");
|
|
11822
|
-
const basename =
|
|
12381
|
+
const basename = path7.basename(normalized).toLowerCase();
|
|
11823
12382
|
return normalized.startsWith("docs/") || normalized.includes("/docs/") || normalized.startsWith(".github/") || basename === "readme.md" || basename === "agents.md" || basename === "claude.md" || basename.endsWith(".md") || basename.endsWith(".mdx") || basename.endsWith(".rst") || basename.endsWith(".adoc");
|
|
11824
12383
|
}
|
|
11825
12384
|
function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
|
|
11826
|
-
const pkg =
|
|
12385
|
+
const pkg = readPackageJson2(repoRoot);
|
|
11827
12386
|
const scripts = pkg?.scripts ?? {};
|
|
11828
12387
|
const recommendations = [
|
|
11829
12388
|
{
|
|
@@ -11847,7 +12406,7 @@ function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
|
|
|
11847
12406
|
if (typeof scripts[script] === "string") {
|
|
11848
12407
|
recommendations.push({
|
|
11849
12408
|
command: `${packageManager} ${script}`,
|
|
11850
|
-
reason: `Repo package.json defines ${script}; run it when docs or agent guidance can affect generated output or
|
|
12409
|
+
reason: `Repo package.json defines ${script}; run it when docs or agent guidance can affect generated output or shared docs.`,
|
|
11851
12410
|
files: [],
|
|
11852
12411
|
source: "script",
|
|
11853
12412
|
confidence: script === "check" ? "low" : "medium"
|
|
@@ -11857,7 +12416,7 @@ function docsOnlyRecommendations(repoRoot, packageManager, changedFiles) {
|
|
|
11857
12416
|
return recommendations.slice(0, 4);
|
|
11858
12417
|
}
|
|
11859
12418
|
function packageScriptRecommendations(repoRoot, packageManager) {
|
|
11860
|
-
const pkg =
|
|
12419
|
+
const pkg = readPackageJson2(repoRoot);
|
|
11861
12420
|
const scripts = pkg?.scripts ?? {};
|
|
11862
12421
|
const commands = [];
|
|
11863
12422
|
for (const script of [
|
|
@@ -11890,7 +12449,7 @@ function taskRelevantPackageScriptRecommendations(repoRoot, packageManager, task
|
|
|
11890
12449
|
const packageRoots = unique(sourceFiles.map((file) => nearestPackageInfo(repoRoot, file)?.root).filter((root) => Boolean(root)));
|
|
11891
12450
|
const recommendations = [];
|
|
11892
12451
|
for (const packageRoot of packageRoots) {
|
|
11893
|
-
const pkg =
|
|
12452
|
+
const pkg = readPackageJson2(packageRoot);
|
|
11894
12453
|
const scripts = pkg?.scripts ?? {};
|
|
11895
12454
|
const packageName = typeof pkg?.name === "string" ? pkg.name : void 0;
|
|
11896
12455
|
if (wantsPackageRelease) {
|
|
@@ -11923,8 +12482,8 @@ function packageScriptCommand(packageManager, packageRoot, repoRoot, packageName
|
|
|
11923
12482
|
}
|
|
11924
12483
|
return packageManager === "npm" ? `npm run ${script}` : `${packageManager} ${script}`;
|
|
11925
12484
|
}
|
|
11926
|
-
function
|
|
11927
|
-
const content = safeRead(
|
|
12485
|
+
function readPackageJson2(packageRoot) {
|
|
12486
|
+
const content = safeRead(path7.join(packageRoot, "package.json"), 2e5);
|
|
11928
12487
|
if (!content)
|
|
11929
12488
|
return null;
|
|
11930
12489
|
try {
|
|
@@ -11940,17 +12499,17 @@ function shellQuote(value) {
|
|
|
11940
12499
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
11941
12500
|
}
|
|
11942
12501
|
function detectPackageManager(repoRoot) {
|
|
11943
|
-
const pkg =
|
|
12502
|
+
const pkg = readPackageJson2(repoRoot);
|
|
11944
12503
|
if (typeof pkg?.packageManager === "string") {
|
|
11945
12504
|
const name = pkg.packageManager.split("@")[0];
|
|
11946
12505
|
if (name)
|
|
11947
12506
|
return name;
|
|
11948
12507
|
}
|
|
11949
|
-
if (existsSync3(
|
|
12508
|
+
if (existsSync3(path7.join(repoRoot, "pnpm-lock.yaml")))
|
|
11950
12509
|
return "pnpm";
|
|
11951
|
-
if (existsSync3(
|
|
12510
|
+
if (existsSync3(path7.join(repoRoot, "yarn.lock")))
|
|
11952
12511
|
return "yarn";
|
|
11953
|
-
if (existsSync3(
|
|
12512
|
+
if (existsSync3(path7.join(repoRoot, "bun.lockb")))
|
|
11954
12513
|
return "bun";
|
|
11955
12514
|
return "npm";
|
|
11956
12515
|
}
|
|
@@ -11967,6 +12526,10 @@ function contextForTask(scan, request) {
|
|
|
11967
12526
|
const files = request.files ?? [];
|
|
11968
12527
|
const maxTokens = request.maxTokens ?? 6e3;
|
|
11969
12528
|
const scoped = files.length > 0;
|
|
12529
|
+
const dependencyRemediation = isDependencyRemediationTask(task, files) ? buildDependencyRemediationPlan(request.repoRoot ?? scan.repoRoot, {
|
|
12530
|
+
task,
|
|
12531
|
+
changedFiles: files
|
|
12532
|
+
}) : void 0;
|
|
11970
12533
|
const rankedRules = rankRules(scan.rules, task, files);
|
|
11971
12534
|
const initialRules = rankedRules.slice(0, scoped ? 8 : 12);
|
|
11972
12535
|
const initialSkills = selectSkills(scan.skills, task, files, scoped).map(compactSkillForContext);
|
|
@@ -11974,13 +12537,18 @@ function contextForTask(scan, request) {
|
|
|
11974
12537
|
const rankedMap = rankMap(mapCandidates, task, files);
|
|
11975
12538
|
const selectedMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(initialRules, task), initialSkills, rankedMap, files, scoped ? 14 : 18).map(compactMapNodeForContext);
|
|
11976
12539
|
const suggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, selectedMap, request.repoRoot);
|
|
12540
|
+
const dependencyTests = dependencyValidationForContext(dependencyRemediation, task);
|
|
12541
|
+
const focusedSuggestedTests = dependencyRemediation ? dependencyTests : suggestedTests;
|
|
11977
12542
|
const initialSelection = {
|
|
11978
12543
|
rules: initialRules,
|
|
11979
12544
|
skills: initialSkills,
|
|
11980
12545
|
map: selectedMap,
|
|
11981
|
-
wikiPages: selectWikiPages(buildContextWiki(scan).pages, task, files, scoped,
|
|
12546
|
+
wikiPages: selectWikiPages(buildContextWiki(scan).pages, task, files, scoped, focusedSuggestedTests).map(compactWikiPageForContext),
|
|
11982
12547
|
memories: rankMemories(scan.memories, task, files).slice(0, scoped ? 4 : 8).map(compactMemoryForContext),
|
|
11983
|
-
suggestedTests
|
|
12548
|
+
suggestedTests: unique([...dependencyTests, ...focusedSuggestedTests]),
|
|
12549
|
+
...dependencyRemediation ? {
|
|
12550
|
+
dependencyRemediation: compactDependencyRemediationForContext(dependencyRemediation)
|
|
12551
|
+
} : {}
|
|
11984
12552
|
};
|
|
11985
12553
|
const { selection, tokenEstimate, omitted } = fitSelectionToBudget(initialSelection, {
|
|
11986
12554
|
maxTokens,
|
|
@@ -11991,32 +12559,39 @@ function contextForTask(scan, request) {
|
|
|
11991
12559
|
const { rules: selectedRules, skills: selectedSkills, map: budgetedMap, wikiPages: selectedWikiPages, memories: selectedMemories } = selection;
|
|
11992
12560
|
const finalMap = takeGuidanceGroundedMapNodes(mapCandidates, scan.sources, rulesStronglyRelevantForGrounding(selectedRules, task), selectedSkills, budgetedMap, files, Math.max(budgetedMap.length, scoped ? 2 : 4)).map(compactMapNodeForContext);
|
|
11993
12561
|
const finalSuggestedTests = selectValidationForContext(scan.map, scan.sources, task, files, finalMap, request.repoRoot);
|
|
12562
|
+
const packetSuggestedTests = unique([
|
|
12563
|
+
...dependencyRemediation ? dependencyValidationForContext(dependencyRemediation, task) : finalSuggestedTests
|
|
12564
|
+
]);
|
|
11994
12565
|
const owners = selectOwners(scan.map, files);
|
|
11995
12566
|
const sources = unique([
|
|
11996
12567
|
...selectedRules.flatMap((rule) => rule.sourcePaths),
|
|
11997
12568
|
...selectedSkills.map((skill) => skill.sourcePath),
|
|
11998
12569
|
...finalMap.map(mapNodeSourcePath),
|
|
11999
12570
|
...selectedWikiPages.flatMap((page2) => page2.sourcePaths),
|
|
12000
|
-
...selectedMemories.flatMap((memory) => memory.sourcePaths)
|
|
12571
|
+
...selectedMemories.flatMap((memory) => memory.sourcePaths),
|
|
12572
|
+
...dependencySourcePaths(dependencyRemediation)
|
|
12001
12573
|
]);
|
|
12002
12574
|
const teamGuidance = scan.teamGuidanceCatalog ? teamGuidanceDeliveryFor(scan, selectedRules, selectedSkills, selectedMemories) : void 0;
|
|
12003
12575
|
const packet = {
|
|
12004
12576
|
packetId: packetId(),
|
|
12005
12577
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12006
12578
|
task,
|
|
12007
|
-
summary: summarizeContext(task, selectedRules,
|
|
12579
|
+
summary: summarizeContext(task, selectedRules, packetSuggestedTests, finalMap),
|
|
12008
12580
|
rules: selectedRules,
|
|
12009
12581
|
skills: selectedSkills,
|
|
12010
12582
|
map: finalMap,
|
|
12011
12583
|
wikiPages: selectedWikiPages,
|
|
12012
12584
|
memories: selectedMemories,
|
|
12013
|
-
suggestedTests:
|
|
12585
|
+
suggestedTests: packetSuggestedTests,
|
|
12014
12586
|
owners,
|
|
12015
12587
|
sources,
|
|
12016
12588
|
tokenEstimate,
|
|
12017
12589
|
omitted,
|
|
12018
|
-
valueSignals: contextValueSignals(finalMap,
|
|
12019
|
-
...teamGuidance ? { teamGuidance } : {}
|
|
12590
|
+
valueSignals: contextValueSignals(finalMap, packetSuggestedTests, sources.length, teamGuidance?.applied.length ?? 0),
|
|
12591
|
+
...teamGuidance ? { teamGuidance } : {},
|
|
12592
|
+
...dependencyRemediation ? {
|
|
12593
|
+
dependencyRemediation: compactDependencyRemediationForContext(dependencyRemediation)
|
|
12594
|
+
} : {}
|
|
12020
12595
|
};
|
|
12021
12596
|
return enforceContextPacketBudget(packet, maxTokens, files);
|
|
12022
12597
|
}
|
|
@@ -12050,6 +12625,47 @@ function contextValueSignals(map, suggestedTests, relevantSourceCount, appliedTe
|
|
|
12050
12625
|
suggestedTestCount: suggestedTests.length
|
|
12051
12626
|
};
|
|
12052
12627
|
}
|
|
12628
|
+
function dependencyValidationForContext(plan, task) {
|
|
12629
|
+
if (!plan)
|
|
12630
|
+
return [];
|
|
12631
|
+
const allowsRuntimeProof = /\b(runtime|smoke|e2e|browser|production|integration)\b/i.test(task);
|
|
12632
|
+
return plan.validationCommands.filter((command2) => allowsRuntimeProof ? true : !/\b(e2e|smoke|ux)\b/i.test(command2)).slice(0, allowsRuntimeProof ? 5 : 4);
|
|
12633
|
+
}
|
|
12634
|
+
function dependencySourcePaths(plan) {
|
|
12635
|
+
if (!plan)
|
|
12636
|
+
return [];
|
|
12637
|
+
return unique([
|
|
12638
|
+
...plan.manifestFiles,
|
|
12639
|
+
...plan.lockfiles,
|
|
12640
|
+
...plan.workspaceFiles,
|
|
12641
|
+
...plan.changedDependencyFiles,
|
|
12642
|
+
...plan.directDependencyFiles
|
|
12643
|
+
]);
|
|
12644
|
+
}
|
|
12645
|
+
function compactDependencyRemediationForContext(plan) {
|
|
12646
|
+
if (!plan)
|
|
12647
|
+
return void 0;
|
|
12648
|
+
return {
|
|
12649
|
+
...plan,
|
|
12650
|
+
manifestFiles: plan.manifestFiles.slice(0, 8),
|
|
12651
|
+
lockfiles: plan.lockfiles.slice(0, 6),
|
|
12652
|
+
workspaceFiles: plan.workspaceFiles.slice(0, 4),
|
|
12653
|
+
changedDependencyFiles: plan.changedDependencyFiles.slice(0, 8),
|
|
12654
|
+
directDependencyFiles: plan.directDependencyFiles.slice(0, 8),
|
|
12655
|
+
packageSignals: plan.packageSignals.slice(0, 8).map((signal2) => ({
|
|
12656
|
+
...signal2,
|
|
12657
|
+
manifestPaths: signal2.manifestPaths.slice(0, 4),
|
|
12658
|
+
lockfilePaths: signal2.lockfilePaths.slice(0, 4),
|
|
12659
|
+
evidence: signal2.evidence.slice(0, 3)
|
|
12660
|
+
})),
|
|
12661
|
+
graphCommands: plan.graphCommands.slice(0, 6),
|
|
12662
|
+
remediationCommands: plan.remediationCommands.slice(0, 6),
|
|
12663
|
+
validationCommands: plan.validationCommands.slice(0, 8),
|
|
12664
|
+
residualRiskChecks: plan.residualRiskChecks.slice(0, 6),
|
|
12665
|
+
handoffRequirements: plan.handoffRequirements.slice(0, 4),
|
|
12666
|
+
summary: truncateText(plan.summary, 520)
|
|
12667
|
+
};
|
|
12668
|
+
}
|
|
12053
12669
|
function codebaseMapForTask(scan, request, limit = 80) {
|
|
12054
12670
|
const files = request.files ?? [];
|
|
12055
12671
|
const hasSpecificScope = files.length > 0 || wordsFor3(request.task).length > 1;
|
|
@@ -12858,8 +13474,8 @@ function sameStem(testPath, sourcePath) {
|
|
|
12858
13474
|
function isTestScriptNode(node) {
|
|
12859
13475
|
return node.type === "workflow" && node.tags.some((tag) => /test|check|type|e2e|smoke/i.test(tag));
|
|
12860
13476
|
}
|
|
12861
|
-
function isTestPathCandidate(
|
|
12862
|
-
return /(^|\/|\.)(test|spec)\.[^.]+$/i.test(
|
|
13477
|
+
function isTestPathCandidate(pathName2) {
|
|
13478
|
+
return /(^|\/|\.)(test|spec)\.[^.]+$/i.test(pathName2);
|
|
12863
13479
|
}
|
|
12864
13480
|
function summarizeContext(task, rules, tests, map = []) {
|
|
12865
13481
|
const implementationPaths = contextSummaryPaths(map);
|
|
@@ -12927,7 +13543,8 @@ function cloneSelection(selection) {
|
|
|
12927
13543
|
map: [...selection.map],
|
|
12928
13544
|
wikiPages: [...selection.wikiPages],
|
|
12929
13545
|
memories: [...selection.memories],
|
|
12930
|
-
suggestedTests: [...selection.suggestedTests]
|
|
13546
|
+
suggestedTests: [...selection.suggestedTests],
|
|
13547
|
+
...selection.dependencyRemediation ? { dependencyRemediation: selection.dependencyRemediation } : {}
|
|
12931
13548
|
};
|
|
12932
13549
|
}
|
|
12933
13550
|
function estimateSelectionTokens(selection, task) {
|
|
@@ -12942,6 +13559,7 @@ function estimateSelectionTokens(selection, task) {
|
|
|
12942
13559
|
wikiPages: selection.wikiPages,
|
|
12943
13560
|
memories: selection.memories,
|
|
12944
13561
|
suggestedTests: selection.suggestedTests,
|
|
13562
|
+
...selection.dependencyRemediation ? { dependencyRemediation: selection.dependencyRemediation } : {},
|
|
12945
13563
|
owners: [],
|
|
12946
13564
|
sources: selectionSources(selection),
|
|
12947
13565
|
tokenEstimate: 0,
|
|
@@ -13038,10 +13656,6 @@ function trimContextPacket(packet, protectedMapPaths) {
|
|
|
13038
13656
|
packet.suggestedTests.pop();
|
|
13039
13657
|
return true;
|
|
13040
13658
|
}
|
|
13041
|
-
if (packet.rules.length > 0) {
|
|
13042
|
-
packet.rules.pop();
|
|
13043
|
-
return true;
|
|
13044
|
-
}
|
|
13045
13659
|
if (packet.map.length > protectedMapPaths.size) {
|
|
13046
13660
|
packet.map.pop();
|
|
13047
13661
|
return true;
|
|
@@ -13136,7 +13750,8 @@ function refreshContextPacketDerivedFields(packet) {
|
|
|
13136
13750
|
map: packet.map,
|
|
13137
13751
|
wikiPages: packet.wikiPages,
|
|
13138
13752
|
memories: packet.memories,
|
|
13139
|
-
suggestedTests: packet.suggestedTests
|
|
13753
|
+
suggestedTests: packet.suggestedTests,
|
|
13754
|
+
...packet.dependencyRemediation ? { dependencyRemediation: packet.dependencyRemediation } : {}
|
|
13140
13755
|
});
|
|
13141
13756
|
refreshTeamGuidanceDelivery(packet);
|
|
13142
13757
|
packet.valueSignals = contextValueSignals(packet.map, packet.suggestedTests, packet.sources.length, packet.teamGuidance?.applied.length ?? 0);
|
|
@@ -13188,7 +13803,8 @@ function selectionSources(selection) {
|
|
|
13188
13803
|
...selection.skills.map((skill) => skill.sourcePath),
|
|
13189
13804
|
...selection.map.map((node) => node.path),
|
|
13190
13805
|
...selection.wikiPages.flatMap((page2) => page2.sourcePaths),
|
|
13191
|
-
...selection.memories.flatMap((memory) => memory.sourcePaths)
|
|
13806
|
+
...selection.memories.flatMap((memory) => memory.sourcePaths),
|
|
13807
|
+
...dependencySourcePaths(selection.dependencyRemediation)
|
|
13192
13808
|
]);
|
|
13193
13809
|
}
|
|
13194
13810
|
function trimSelection(selection, minimums, protectedMapPaths) {
|
|
@@ -13333,6 +13949,7 @@ var init_context = __esm({
|
|
|
13333
13949
|
init_scanner();
|
|
13334
13950
|
init_contextWiki();
|
|
13335
13951
|
init_pathIntent();
|
|
13952
|
+
init_dependencyRemediation();
|
|
13336
13953
|
init_testRecommendations();
|
|
13337
13954
|
init_utils();
|
|
13338
13955
|
contextStopWords = /* @__PURE__ */ new Set([
|
|
@@ -13405,440 +14022,328 @@ var init_context = __esm({
|
|
|
13405
14022
|
}
|
|
13406
14023
|
});
|
|
13407
14024
|
|
|
13408
|
-
// ../core/dist/
|
|
13409
|
-
import
|
|
13410
|
-
function
|
|
13411
|
-
const
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
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 };
|
|
13436
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");
|
|
13437
14120
|
return {
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
manifestFiles: manifests.slice(0, 20),
|
|
13447
|
-
lockfiles,
|
|
13448
|
-
workspaceFiles,
|
|
13449
|
-
changedDependencyFiles,
|
|
13450
|
-
directDependencyFiles,
|
|
13451
|
-
packageSignals,
|
|
13452
|
-
graphCommands,
|
|
13453
|
-
remediationCommands,
|
|
13454
|
-
validationCommands: validationCommands2,
|
|
13455
|
-
residualRiskChecks,
|
|
13456
|
-
handoffRequirements: [
|
|
13457
|
-
"List advisory ids or scanner finding ids that were fixed.",
|
|
13458
|
-
"Name packages still present in the lockfile with vulnerable versions, or state that the scan/audit is clean.",
|
|
13459
|
-
"Explain any major-version upgrades, overrides, peer dependency changes, skipped advisories, or remaining advisory ids.",
|
|
13460
|
-
"Attach validation commands and CI follow-up results before marking the security mission done."
|
|
13461
|
-
],
|
|
13462
|
-
summary: summaryFor3({
|
|
13463
|
-
packageManager,
|
|
13464
|
-
manifests,
|
|
13465
|
-
lockfiles,
|
|
13466
|
-
changedDependencyFiles,
|
|
13467
|
-
packageSignals,
|
|
13468
|
-
validationCommands: validationCommands2
|
|
13469
|
-
}),
|
|
13470
|
-
confidence
|
|
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."
|
|
13471
14129
|
};
|
|
13472
14130
|
}
|
|
13473
|
-
function
|
|
13474
|
-
const
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
const
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
const
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
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
|
+
};
|
|
13521
14189
|
}
|
|
13522
|
-
function
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
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
|
|
13536
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;
|
|
13537
14248
|
}
|
|
13538
|
-
function
|
|
13539
|
-
const
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
continue;
|
|
13543
|
-
const parsed = readPackageJson2(repoRoot, manifest);
|
|
13544
|
-
if (!parsed)
|
|
13545
|
-
continue;
|
|
13546
|
-
for (const name of packageJsonDependencyNames(parsed)) {
|
|
13547
|
-
const current = byPackage.get(name) ?? [];
|
|
13548
|
-
current.push(manifest);
|
|
13549
|
-
byPackage.set(name, current);
|
|
13550
|
-
}
|
|
13551
|
-
}
|
|
13552
|
-
return byPackage;
|
|
13553
|
-
}
|
|
13554
|
-
function packageJsonDependencyNames(parsed) {
|
|
13555
|
-
return unique([
|
|
13556
|
-
"dependencies",
|
|
13557
|
-
"devDependencies",
|
|
13558
|
-
"optionalDependencies",
|
|
13559
|
-
"peerDependencies",
|
|
13560
|
-
"overrides",
|
|
13561
|
-
"resolutions",
|
|
13562
|
-
"pnpm"
|
|
13563
|
-
].flatMap((field) => nestedDependencyNames(parsed[field])));
|
|
13564
|
-
}
|
|
13565
|
-
function nestedDependencyNames(value) {
|
|
13566
|
-
if (!value || typeof value !== "object")
|
|
13567
|
-
return [];
|
|
13568
|
-
return Object.entries(value).flatMap(([key, nested]) => [
|
|
13569
|
-
looksLikePackageName(key) ? key : "",
|
|
13570
|
-
...nestedDependencyNames(nested)
|
|
13571
|
-
]);
|
|
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);
|
|
13572
14253
|
}
|
|
13573
|
-
function
|
|
13574
|
-
|
|
13575
|
-
const quoted = text.matchAll(/[`'"](@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)[`'"]/gi);
|
|
13576
|
-
for (const match of quoted)
|
|
13577
|
-
names.push(match[1] ?? "");
|
|
13578
|
-
const advisoryContext = text.matchAll(/\b(?:package|dependency|upgrade|update|override|transitive|vulnerable|dependabot|cve|ghsa|audit)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
|
|
13579
|
-
for (const match of advisoryContext)
|
|
13580
|
-
names.push(match[1] ?? "");
|
|
13581
|
-
const atVersion = text.matchAll(/\b(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/gi);
|
|
13582
|
-
for (const match of atVersion)
|
|
13583
|
-
names.push(match[1] ?? "");
|
|
13584
|
-
if (/\b(dependabot|cve-|ghsa-|audit|vulnerab|advisory)\b/i.test(text)) {
|
|
13585
|
-
const affectedPackageContext = text.matchAll(/\b(?:for|affects?|in)\s+(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)/gi);
|
|
13586
|
-
for (const match of affectedPackageContext)
|
|
13587
|
-
names.push(match[1] ?? "");
|
|
13588
|
-
}
|
|
13589
|
-
return unique(names.map(cleanPackageName).filter(looksLikePackageName));
|
|
14254
|
+
function statusLabel(status) {
|
|
14255
|
+
return status === "approved" ? "[approved]" : "[proposed]";
|
|
13590
14256
|
}
|
|
13591
|
-
function
|
|
13592
|
-
const
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
if (packageJsonMatch?.[1])
|
|
13598
|
-
names.push(packageJsonMatch[1]);
|
|
13599
|
-
const pnpmMatch = line.match(/^\s*[+-]\s{0,8}(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
|
|
13600
|
-
if (pnpmMatch?.[1])
|
|
13601
|
-
names.push(pnpmMatch[1]);
|
|
13602
|
-
const lockPathMatch = line.match(/\/(@?[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9._-]+)?)@(?:\^|~)?\d/i);
|
|
13603
|
-
if (lockPathMatch?.[1])
|
|
13604
|
-
names.push(lockPathMatch[1]);
|
|
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.`);
|
|
13605
14263
|
}
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
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
|
+
};
|
|
13613
14274
|
}
|
|
13614
|
-
function
|
|
13615
|
-
|
|
13616
|
-
if (!content)
|
|
13617
|
-
return null;
|
|
14275
|
+
function assertPublicResearchUrl(value) {
|
|
14276
|
+
let parsed;
|
|
13618
14277
|
try {
|
|
13619
|
-
|
|
13620
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
14278
|
+
parsed = new URL(value);
|
|
13621
14279
|
} catch {
|
|
13622
|
-
|
|
13623
|
-
}
|
|
13624
|
-
}
|
|
13625
|
-
function changeKindFor(packageName, diff) {
|
|
13626
|
-
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13627
|
-
const removedVersions = [
|
|
13628
|
-
...diff.matchAll(new RegExp(`^-.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
|
|
13629
|
-
].map((match) => match[1] ?? "");
|
|
13630
|
-
const addedVersions = [
|
|
13631
|
-
...diff.matchAll(new RegExp(`^\\+.*${escaped}.*?(\\d+\\.\\d+\\.\\d+)`, "gim"))
|
|
13632
|
-
].map((match) => match[1] ?? "");
|
|
13633
|
-
const removed = removedVersions[0];
|
|
13634
|
-
const added = addedVersions[0];
|
|
13635
|
-
if (removed && added) {
|
|
13636
|
-
const [removedMajor] = removed.split(".");
|
|
13637
|
-
const [addedMajor] = added.split(".");
|
|
13638
|
-
return removedMajor && addedMajor && removedMajor !== addedMajor ? "major" : "patch_or_minor";
|
|
14280
|
+
throw new Error("Product research URLs must be valid HTTPS URLs.");
|
|
13639
14281
|
}
|
|
13640
|
-
if (
|
|
13641
|
-
|
|
13642
|
-
return "unknown";
|
|
13643
|
-
}
|
|
13644
|
-
function manifestsForPackage(packageName, manifestDeps) {
|
|
13645
|
-
return unique([
|
|
13646
|
-
...manifestDeps.get(packageName) ?? [],
|
|
13647
|
-
...[...manifestDeps.entries()].filter(([name]) => name.endsWith(`/${packageName}`)).flatMap(([, manifests]) => manifests)
|
|
13648
|
-
]).slice(0, 8);
|
|
13649
|
-
}
|
|
13650
|
-
function evidenceForPackage(packageName, task, diff) {
|
|
13651
|
-
const evidence = [];
|
|
13652
|
-
if (task.toLowerCase().includes(packageName.toLowerCase())) {
|
|
13653
|
-
evidence.push("mentioned_in_task");
|
|
14282
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password) {
|
|
14283
|
+
throw new Error("Product research URLs must use HTTPS and cannot include credentials.");
|
|
13654
14284
|
}
|
|
13655
|
-
|
|
13656
|
-
|
|
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.");
|
|
13657
14288
|
}
|
|
13658
|
-
if (evidence.length === 0)
|
|
13659
|
-
evidence.push("manifest_candidate");
|
|
13660
|
-
return evidence;
|
|
13661
14289
|
}
|
|
13662
|
-
function
|
|
13663
|
-
const
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
...scriptCommand(scripts, "check", run),
|
|
13668
|
-
...scriptCommand(scripts, "test", run),
|
|
13669
|
-
...scriptCommand(scripts, "test:client", run),
|
|
13670
|
-
...scriptCommand(scripts, "test:e2e:smoke", run),
|
|
13671
|
-
...scriptCommand(scripts, "build", run)
|
|
13672
|
-
]).slice(0, 10);
|
|
13673
|
-
}
|
|
13674
|
-
function packageScripts(repoRoot) {
|
|
13675
|
-
const parsed = readPackageJson2(repoRoot, "package.json");
|
|
13676
|
-
const scripts = parsed?.scripts;
|
|
13677
|
-
return scripts && typeof scripts === "object" ? Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string")) : {};
|
|
13678
|
-
}
|
|
13679
|
-
function scriptCommand(scripts, scriptName, runCommand) {
|
|
13680
|
-
return scripts[scriptName] ? [`${runCommand} ${scriptName}`] : [];
|
|
13681
|
-
}
|
|
13682
|
-
function packageRunCommand(packageManager) {
|
|
13683
|
-
switch (packageManager) {
|
|
13684
|
-
case "npm":
|
|
13685
|
-
return "npm run";
|
|
13686
|
-
case "yarn":
|
|
13687
|
-
return "yarn";
|
|
13688
|
-
case "bun":
|
|
13689
|
-
return "bun run";
|
|
13690
|
-
default:
|
|
13691
|
-
return "pnpm";
|
|
13692
|
-
}
|
|
13693
|
-
}
|
|
13694
|
-
function auditCommandsFor(packageManager) {
|
|
13695
|
-
switch (packageManager) {
|
|
13696
|
-
case "pnpm":
|
|
13697
|
-
return ["pnpm audit --prod"];
|
|
13698
|
-
case "npm":
|
|
13699
|
-
return ["npm audit --omit=dev"];
|
|
13700
|
-
case "yarn":
|
|
13701
|
-
return ["yarn npm audit --environment production"];
|
|
13702
|
-
case "bun":
|
|
13703
|
-
return ["bun audit"];
|
|
13704
|
-
case "pip":
|
|
13705
|
-
return ["pip-audit"];
|
|
13706
|
-
case "bundler":
|
|
13707
|
-
return ["bundle audit check"];
|
|
13708
|
-
case "go":
|
|
13709
|
-
return ["govulncheck ./..."];
|
|
13710
|
-
case "cargo":
|
|
13711
|
-
return ["cargo audit"];
|
|
13712
|
-
default:
|
|
13713
|
-
return [];
|
|
13714
|
-
}
|
|
13715
|
-
}
|
|
13716
|
-
function dependencyGraphCommandsFor(packageManager, packageNames) {
|
|
13717
|
-
const selected = packageNames.slice(0, 5);
|
|
13718
|
-
if (selected.length === 0)
|
|
13719
|
-
return [];
|
|
13720
|
-
switch (packageManager) {
|
|
13721
|
-
case "pnpm":
|
|
13722
|
-
return selected.map((name) => `pnpm why ${name}`);
|
|
13723
|
-
case "npm":
|
|
13724
|
-
return selected.map((name) => `npm explain ${name}`);
|
|
13725
|
-
case "yarn":
|
|
13726
|
-
return selected.map((name) => `yarn why ${name}`);
|
|
13727
|
-
case "bun":
|
|
13728
|
-
return selected.map((name) => `bun pm why ${name}`);
|
|
13729
|
-
case "go":
|
|
13730
|
-
return selected.map((name) => `go mod why -m ${name}`);
|
|
13731
|
-
case "cargo":
|
|
13732
|
-
return selected.map((name) => `cargo tree -i ${name}`);
|
|
13733
|
-
default:
|
|
13734
|
-
return [];
|
|
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;
|
|
13735
14295
|
}
|
|
14296
|
+
return family === 6 && (host.startsWith("fc") || host.startsWith("fd") || host === "::1");
|
|
13736
14297
|
}
|
|
13737
|
-
function
|
|
13738
|
-
const
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
case "yarn":
|
|
13747
|
-
return selected.map((name) => `yarn up ${name}`);
|
|
13748
|
-
case "bun":
|
|
13749
|
-
return selected.map((name) => `bun update ${name}`);
|
|
13750
|
-
case "go":
|
|
13751
|
-
return selected.map((name) => `go get ${name}@latest`);
|
|
13752
|
-
case "cargo":
|
|
13753
|
-
return selected.map((name) => `cargo update -p ${name}`);
|
|
13754
|
-
default:
|
|
13755
|
-
return [];
|
|
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
|
+
}
|
|
13756
14307
|
}
|
|
14308
|
+
return merged.slice(0, 12);
|
|
13757
14309
|
}
|
|
13758
|
-
function
|
|
13759
|
-
return
|
|
13760
|
-
packageNames.length > 0 ? `Confirm vulnerable versions are gone for: ${packageNames.slice(0, 8).join(", ")}.` : "Name each advisory/package from the scanner before editing.",
|
|
13761
|
-
changedDependencyFiles.some(isDependencyLockfile) ? "Inspect the lockfile diff for unexpected package churn outside the advisory path." : "Regenerate and inspect the lockfile if the fix changes dependency resolution.",
|
|
13762
|
-
"Split major-version upgrades or broad framework upgrades into a separate mission unless the advisory requires them.",
|
|
13763
|
-
"Record any remaining advisory ids, ignored dev-only findings, or transitive paths that still need owner judgment.",
|
|
13764
|
-
...auditCommandsFor(packageManager).map((command2) => `Rerun ${command2} after the lockfile update.`)
|
|
13765
|
-
]);
|
|
14310
|
+
function normalizeText(value, maxLength) {
|
|
14311
|
+
return value.trim().replace(/\s+/g, " ").slice(0, maxLength);
|
|
13766
14312
|
}
|
|
13767
|
-
function
|
|
13768
|
-
|
|
13769
|
-
if (input.changedDependencyFiles.length > 0)
|
|
13770
|
-
score += 20;
|
|
13771
|
-
if (input.packageSignals.length > 0)
|
|
13772
|
-
score += 20;
|
|
13773
|
-
if (input.lockfiles.length > 0)
|
|
13774
|
-
score += 10;
|
|
13775
|
-
if (input.validationCommands.length > 0)
|
|
13776
|
-
score += 10;
|
|
13777
|
-
return Math.min(95, score);
|
|
14313
|
+
function normalizeKey(value) {
|
|
14314
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
13778
14315
|
}
|
|
13779
|
-
function
|
|
13780
|
-
|
|
13781
|
-
return [
|
|
13782
|
-
`Detected ${input.packageManager} dependency remediation context.`,
|
|
13783
|
-
`${input.manifests.length} manifest${input.manifests.length === 1 ? "" : "s"}, ${input.lockfiles.length} lockfile${input.lockfiles.length === 1 ? "" : "s"}.`,
|
|
13784
|
-
input.changedDependencyFiles.length > 0 ? `Changed dependency files: ${input.changedDependencyFiles.slice(0, 6).join(", ")}.` : "No dependency files are changed yet.",
|
|
13785
|
-
packages.length > 0 ? `Likely packages: ${packages.slice(0, 8).join(", ")}.` : "Package names still need advisory/scanner evidence.",
|
|
13786
|
-
input.validationCommands.length > 0 ? `Validation starts with ${input.validationCommands[0]}.` : "Validation commands need repo-specific confirmation."
|
|
13787
|
-
].join(" ");
|
|
14316
|
+
function clampConfidence(value) {
|
|
14317
|
+
return Math.max(0, Math.min(1, Number.isFinite(value) ? value : 0));
|
|
13788
14318
|
}
|
|
13789
|
-
function
|
|
13790
|
-
|
|
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");
|
|
13791
14324
|
}
|
|
13792
|
-
function
|
|
13793
|
-
|
|
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";
|
|
13794
14330
|
}
|
|
13795
|
-
function
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
"change",
|
|
13801
|
-
"changes",
|
|
13802
|
-
"dependency",
|
|
13803
|
-
"dependencies",
|
|
13804
|
-
"dependabot",
|
|
13805
|
-
"finding",
|
|
13806
|
-
"findings",
|
|
13807
|
-
"package",
|
|
13808
|
-
"packages",
|
|
13809
|
-
"security",
|
|
13810
|
-
"transitive",
|
|
13811
|
-
"upgrade",
|
|
13812
|
-
"upgrades",
|
|
13813
|
-
"vulnerabilities",
|
|
13814
|
-
"vulnerability",
|
|
13815
|
-
"vulnerable"
|
|
13816
|
-
].includes(normalized);
|
|
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";
|
|
13817
14336
|
}
|
|
13818
|
-
var
|
|
13819
|
-
var
|
|
13820
|
-
"../core/dist/
|
|
14337
|
+
var productIntelligenceFile, staleMarketDays, staleProductDays;
|
|
14338
|
+
var init_productIntelligence = __esm({
|
|
14339
|
+
"../core/dist/productIntelligence.js"() {
|
|
13821
14340
|
"use strict";
|
|
14341
|
+
init_localCache();
|
|
14342
|
+
init_localLearning();
|
|
13822
14343
|
init_utils();
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
"pnpm-workspace.yaml",
|
|
13827
|
-
"package-lock.json",
|
|
13828
|
-
"yarn.lock",
|
|
13829
|
-
"bun.lock",
|
|
13830
|
-
"npm-shrinkwrap.json",
|
|
13831
|
-
"requirements.txt",
|
|
13832
|
-
"poetry.lock",
|
|
13833
|
-
"pyproject.toml",
|
|
13834
|
-
"Pipfile.lock",
|
|
13835
|
-
"Gemfile",
|
|
13836
|
-
"Gemfile.lock",
|
|
13837
|
-
"go.mod",
|
|
13838
|
-
"go.sum",
|
|
13839
|
-
"Cargo.toml",
|
|
13840
|
-
"Cargo.lock"
|
|
13841
|
-
];
|
|
14344
|
+
productIntelligenceFile = "product-intelligence.json";
|
|
14345
|
+
staleMarketDays = 90;
|
|
14346
|
+
staleProductDays = 180;
|
|
13842
14347
|
}
|
|
13843
14348
|
});
|
|
13844
14349
|
|
|
@@ -13863,6 +14368,16 @@ function buildProductOperatingContext(scan, request) {
|
|
|
13863
14368
|
});
|
|
13864
14369
|
const status = statusFor(taskClasses, reuseBeforeBuild, productRules);
|
|
13865
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
|
+
};
|
|
13866
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.`;
|
|
13867
14382
|
const compactText = compactTextFor({
|
|
13868
14383
|
summary,
|
|
@@ -13872,6 +14387,7 @@ function buildProductOperatingContext(scan, request) {
|
|
|
13872
14387
|
acceptanceCriteria,
|
|
13873
14388
|
terminology,
|
|
13874
14389
|
stopSignals: stopSignals2,
|
|
14390
|
+
productIntelligence,
|
|
13875
14391
|
maxLines: request.maxLines ?? 32
|
|
13876
14392
|
});
|
|
13877
14393
|
return {
|
|
@@ -13891,9 +14407,10 @@ function buildProductOperatingContext(scan, request) {
|
|
|
13891
14407
|
validation,
|
|
13892
14408
|
productMemoryCandidates,
|
|
13893
14409
|
stopSignals: stopSignals2,
|
|
14410
|
+
productIntelligence,
|
|
13894
14411
|
compactText,
|
|
13895
14412
|
lineCount: compactText.split(/\r?\n/).length,
|
|
13896
|
-
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}`
|
|
13897
14414
|
};
|
|
13898
14415
|
}
|
|
13899
14416
|
function needsProductOperatingContext(request) {
|
|
@@ -14351,6 +14868,7 @@ function compactTextFor(input) {
|
|
|
14351
14868
|
...input.terminology.preferred.length > 0 ? [
|
|
14352
14869
|
`Terminology: use ${input.terminology.preferred.slice(0, 8).join(", ")}.`
|
|
14353
14870
|
] : [],
|
|
14871
|
+
...input.productIntelligence.status !== "not_available" ? input.productIntelligence.compactText.split(/\r?\n/) : [],
|
|
14354
14872
|
"Stop if:",
|
|
14355
14873
|
...itemLines(input.stopSignals, (item) => item, "No stop signals.", 4)
|
|
14356
14874
|
];
|
|
@@ -14379,6 +14897,7 @@ var init_productOperatingContext = __esm({
|
|
|
14379
14897
|
"../core/dist/productOperatingContext.js"() {
|
|
14380
14898
|
"use strict";
|
|
14381
14899
|
init_utils();
|
|
14900
|
+
init_productIntelligence();
|
|
14382
14901
|
PRODUCT_KEYWORDS = [
|
|
14383
14902
|
"app",
|
|
14384
14903
|
"cta",
|
|
@@ -14387,6 +14906,16 @@ var init_productOperatingContext = __esm({
|
|
|
14387
14906
|
"feature",
|
|
14388
14907
|
"flow",
|
|
14389
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",
|
|
14390
14919
|
"page",
|
|
14391
14920
|
"product",
|
|
14392
14921
|
"screen",
|
|
@@ -16624,6 +17153,9 @@ function recipeFor(explicitRecipe, task, request) {
|
|
|
16624
17153
|
return missionRecipes.find((recipe) => recipe.id === explicitRecipe) ?? missionRecipes[0];
|
|
16625
17154
|
}
|
|
16626
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
|
+
}
|
|
16627
17159
|
if (isDesignLedChange(lower)) {
|
|
16628
17160
|
return recipeById("design_change");
|
|
16629
17161
|
}
|
|
@@ -16974,6 +17506,9 @@ function missionSummary(repoName, recipe, autonomy, request) {
|
|
|
16974
17506
|
if (recipe.id === "qa_edge_cases") {
|
|
16975
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.`;
|
|
16976
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
|
+
}
|
|
16977
17512
|
if (recipe.id === "performance") {
|
|
16978
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.`;
|
|
16979
17514
|
}
|
|
@@ -17116,6 +17651,15 @@ function missionGates(autonomy, recipe, options2) {
|
|
|
17116
17651
|
failureMode: "Do not add low-signal tests or hide flaky checks. Ask for the behavior risk and validation target."
|
|
17117
17652
|
});
|
|
17118
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
|
+
}
|
|
17119
17663
|
if (recipe.id === "performance") {
|
|
17120
17664
|
gates.push({
|
|
17121
17665
|
id: "benchmark_baseline",
|
|
@@ -17164,7 +17708,7 @@ function missionGates(autonomy, recipe, options2) {
|
|
|
17164
17708
|
label: "Library privacy gate",
|
|
17165
17709
|
requiredTool: "brief.agent_session_insights",
|
|
17166
17710
|
passCriteria: "Every candidate example is classified as derived-only, redacted transcript, or explicitly approved raw import before sharing.",
|
|
17167
|
-
failureMode: "Do not
|
|
17711
|
+
failureMode: "Do not share raw prompts, secrets, customer data, private logs, or sensitive repo context without explicit approval."
|
|
17168
17712
|
}, {
|
|
17169
17713
|
id: "reuse_quality",
|
|
17170
17714
|
label: "Reuse quality gate",
|
|
@@ -17230,6 +17774,17 @@ function missionSteps(task, autonomy, recipe, options2) {
|
|
|
17230
17774
|
gateId: "plan"
|
|
17231
17775
|
}
|
|
17232
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
|
+
}
|
|
17233
17788
|
if (recipe.id === "ticket_write") {
|
|
17234
17789
|
steps.push({
|
|
17235
17790
|
id: "capture_judgment",
|
|
@@ -17548,7 +18103,7 @@ function missionSteps(task, autonomy, recipe, options2) {
|
|
|
17548
18103
|
id: "verify_receipts",
|
|
17549
18104
|
title: "Verify ticket receipt ledger",
|
|
17550
18105
|
objective: "Prove the ticket-writing receipt chain before another agent runs the ticket.",
|
|
17551
|
-
agentAction: "Run Brief receipt verification and fix missing, mismatched, or broken-chain ticket proof before
|
|
18106
|
+
agentAction: "Run Brief receipt verification and fix missing, mismatched, or broken-chain ticket proof before sending the ticket.",
|
|
17552
18107
|
briefTool: "brief.verify_receipts",
|
|
17553
18108
|
expectedOutput: "Receipt ledger verification with digest and chain-link status.",
|
|
17554
18109
|
gateId: "ticket"
|
|
@@ -17670,6 +18225,17 @@ function selectMissionValidation(suggestedTests, recipe, needsRuntimeEvidence, n
|
|
|
17670
18225
|
"Run brief.review_diff before PR handoff."
|
|
17671
18226
|
]);
|
|
17672
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
|
+
}
|
|
17673
18239
|
if (recipe.id === "performance") {
|
|
17674
18240
|
return unique([
|
|
17675
18241
|
"Benchmark baseline names workload, command, metric, variance, and success threshold before edits.",
|
|
@@ -17759,25 +18325,31 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17759
18325
|
const isTicketWriter = mode === "ticket_writer";
|
|
17760
18326
|
const isTicketFix = mode === "ticket_fix";
|
|
17761
18327
|
const isRuntimeFix = mode === "runtime_fix";
|
|
18328
|
+
const isWebDogfood = recipe.id === "web_dogfood";
|
|
17762
18329
|
const isSecurityRecipe = recipe.id === "security_dependency" || recipe.id === "security_finding" || recipe.id === "security_testing";
|
|
17763
18330
|
const handoffTarget = deliveryReviewTarget(deliveryMode);
|
|
17764
18331
|
const phases = [
|
|
17765
18332
|
{
|
|
17766
|
-
id: isTicketWriter ? "ticket_intake" : "context_map",
|
|
17767
|
-
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",
|
|
17768
18335
|
required: true,
|
|
17769
18336
|
tool: isTicketWriter ? "brief.write_ticket" : "brief.context_for_task + brief.codebase_map",
|
|
17770
|
-
expectedArtifact: isTicketWriter ? "generatedArtifacts.automationTicket" : "context_receipt + map_proof",
|
|
18337
|
+
expectedArtifact: isTicketWriter ? "generatedArtifacts.automationTicket" : isWebDogfood ? "context_receipt + map_proof + browser target" : "context_receipt + map_proof",
|
|
17771
18338
|
proofRequired: isTicketWriter ? [
|
|
17772
18339
|
"ticketReadiness.status",
|
|
17773
18340
|
"automationTicket.machineReadableFields",
|
|
17774
18341
|
"automationTicket.executionContract"
|
|
18342
|
+
] : isWebDogfood ? [
|
|
18343
|
+
"contextReceipt.packetId",
|
|
18344
|
+
"executionContract.evidenceContract.mapProof",
|
|
18345
|
+
"context.likelyFiles",
|
|
18346
|
+
"browser target and safe starting state"
|
|
17775
18347
|
] : [
|
|
17776
18348
|
"contextReceipt.packetId",
|
|
17777
18349
|
"executionContract.evidenceContract.mapProof",
|
|
17778
18350
|
"context.likelyFiles"
|
|
17779
18351
|
],
|
|
17780
|
-
stopIfMissing: isTicketWriter ? "Do not
|
|
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."
|
|
17781
18353
|
},
|
|
17782
18354
|
...isRuntimeFix ? [
|
|
17783
18355
|
{
|
|
@@ -17797,6 +18369,23 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17797
18369
|
stopIfMissing: "Do not code from a runtime symptom until the source, identifier, environment, time window, observed facts, and privacy boundary are present."
|
|
17798
18370
|
}
|
|
17799
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
|
+
] : [],
|
|
17800
18389
|
{
|
|
17801
18390
|
id: "plan_loop",
|
|
17802
18391
|
title: "Convert the work into a bounded loop",
|
|
@@ -17813,53 +18402,70 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17813
18402
|
},
|
|
17814
18403
|
...isTicketWriter ? [] : [
|
|
17815
18404
|
{
|
|
17816
|
-
id: "execute_change",
|
|
17817
|
-
title: "Make the smallest bounded change",
|
|
17818
|
-
required: autonomy !== "read_only",
|
|
17819
|
-
tool: "coding agent",
|
|
17820
|
-
expectedArtifact: "local diff or no-code-change explanation",
|
|
17821
|
-
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
|
+
] : [
|
|
17822
18415
|
"changedFiles[]",
|
|
17823
18416
|
"rootCause or explicit no-root-cause finding",
|
|
17824
18417
|
"acceptanceCriteriaStatus[]"
|
|
17825
18418
|
],
|
|
17826
|
-
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."
|
|
17827
18420
|
},
|
|
17828
18421
|
{
|
|
17829
|
-
id: "validate_change",
|
|
17830
|
-
title: "Run the validation loop",
|
|
18422
|
+
id: isWebDogfood ? "review_evidence" : "validate_change",
|
|
18423
|
+
title: isWebDogfood ? "Review and classify the evidence" : "Run the validation loop",
|
|
17831
18424
|
required: true,
|
|
17832
|
-
tool: "brief.find_tests + project commands",
|
|
17833
|
-
expectedArtifact: "validation evidence",
|
|
17834
|
-
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) : [
|
|
17835
18432
|
"selected validation command",
|
|
17836
18433
|
"pass/fail/skipped result",
|
|
17837
18434
|
"environment note"
|
|
17838
18435
|
],
|
|
17839
|
-
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.`
|
|
17840
18437
|
},
|
|
17841
18438
|
{
|
|
17842
|
-
id: "challenge_review",
|
|
17843
|
-
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",
|
|
17844
18441
|
required: true,
|
|
17845
|
-
tool: "brief.review_diff",
|
|
17846
|
-
expectedArtifact: "review receipt",
|
|
17847
|
-
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
|
+
] : [
|
|
17848
18449
|
"review.verdict",
|
|
17849
18450
|
"review.readiness.status",
|
|
17850
18451
|
"review receipt id",
|
|
17851
18452
|
"blocker/high findings resolved or explicitly accepted"
|
|
17852
18453
|
],
|
|
17853
|
-
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."
|
|
17854
18455
|
}
|
|
17855
18456
|
],
|
|
17856
18457
|
{
|
|
17857
18458
|
id: "handoff_receipts",
|
|
17858
|
-
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",
|
|
17859
18460
|
required: true,
|
|
17860
18461
|
tool: "brief.save_handoff + brief.verify_receipts",
|
|
17861
|
-
expectedArtifact: "proof bundle + receipt ledger verification",
|
|
17862
|
-
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
|
+
] : [
|
|
17863
18469
|
"proofBundle.status",
|
|
17864
18470
|
"proofBundle.receipts.handoffId",
|
|
17865
18471
|
"proofBundle.receipts.reviewReceiptId",
|
|
@@ -17893,7 +18499,7 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17893
18499
|
"MCP-capable IDE",
|
|
17894
18500
|
"console loop recipe"
|
|
17895
18501
|
],
|
|
17896
|
-
evidenceSources: automationLoopEvidenceSources(mode, options2),
|
|
18502
|
+
evidenceSources: automationLoopEvidenceSources(mode, options2, recipe.id),
|
|
17897
18503
|
dataBoundaries: [
|
|
17898
18504
|
"Use derived or redacted runtime facts in prompts; do not paste raw customer payloads, secrets, tokens, or private logs.",
|
|
17899
18505
|
"Keep observed facts, hypotheses, and human decisions separate in the ticket or handoff.",
|
|
@@ -17910,7 +18516,10 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17910
18516
|
"Root cause is inferred from symptoms rather than proven by source, CI, logs, traces, or reproduction.",
|
|
17911
18517
|
"Two plausible approaches compete or the change touches architecture, data, auth, migrations, billing, or customer-visible behavior.",
|
|
17912
18518
|
"The agent changes files outside the mapped scope or selected autonomy.",
|
|
17913
|
-
"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
|
+
] : [],
|
|
17914
18523
|
...isRuntimeFix ? [
|
|
17915
18524
|
"Runtime evidence is stale, contradictory, privacy-sensitive, or cannot be tied to the changed code path."
|
|
17916
18525
|
] : [],
|
|
@@ -17940,7 +18549,7 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17940
18549
|
] : []
|
|
17941
18550
|
]),
|
|
17942
18551
|
finalUpdate: {
|
|
17943
|
-
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"),
|
|
17944
18553
|
requiredSections: isTicketWriter ? [
|
|
17945
18554
|
"Intent",
|
|
17946
18555
|
"Automation Readiness",
|
|
@@ -17949,26 +18558,42 @@ function automationLoopContract(autonomy, recipe, validation, options2, delivery
|
|
|
17949
18558
|
"Brief Evidence Contract",
|
|
17950
18559
|
"Acceptance Criteria",
|
|
17951
18560
|
"Stop Conditions"
|
|
18561
|
+
] : isWebDogfood ? [
|
|
18562
|
+
"Journeys Tested",
|
|
18563
|
+
"What Worked",
|
|
18564
|
+
"Findings",
|
|
18565
|
+
"Environment Limits",
|
|
18566
|
+
"Top Fixes",
|
|
18567
|
+
"Reusable Learning"
|
|
17952
18568
|
] : ticketIssueUpdateSections(),
|
|
17953
18569
|
proofFields: [
|
|
17954
18570
|
"proofBundle.status",
|
|
17955
18571
|
"proofBundle.receipts.handoffId",
|
|
17956
18572
|
"proofBundle.receipts.contextReceiptId",
|
|
17957
|
-
|
|
17958
|
-
|
|
18573
|
+
...isWebDogfood ? [] : [
|
|
18574
|
+
"proofBundle.receipts.reviewReceiptId",
|
|
18575
|
+
"proofBundle.receipts.reviewVerdict"
|
|
18576
|
+
],
|
|
17959
18577
|
"proofBundle.validation.passed",
|
|
17960
18578
|
"proofBundle.missingEvidence"
|
|
17961
18579
|
],
|
|
17962
18580
|
postOnlyWhen: [
|
|
17963
|
-
|
|
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."],
|
|
17964
18584
|
"brief.verify_receipts is trusted or context_only with gaps explained.",
|
|
17965
|
-
"
|
|
17966
|
-
|
|
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
|
+
]
|
|
17967
18589
|
],
|
|
17968
18590
|
doNotPostWhen: unique([
|
|
17969
18591
|
"Required owner, product, security, data, or rollout judgment is missing.",
|
|
17970
18592
|
"Validation is missing and no human owner accepted the gap.",
|
|
17971
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
|
+
] : [],
|
|
17972
18597
|
...isRuntimeFix ? [
|
|
17973
18598
|
"Runtime evidence packet is missing, stale, contradictory, or contains unresolved privacy gaps."
|
|
17974
18599
|
] : [],
|
|
@@ -17989,8 +18614,15 @@ function automationLoopMode(recipe, options2) {
|
|
|
17989
18614
|
return "ticket_fix";
|
|
17990
18615
|
return "mission";
|
|
17991
18616
|
}
|
|
17992
|
-
function automationLoopEvidenceSources(mode, options2) {
|
|
18617
|
+
function automationLoopEvidenceSources(mode, options2, recipeId) {
|
|
17993
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
|
+
] : [],
|
|
17994
18626
|
...mode === "ticket_writer" ? [
|
|
17995
18627
|
"ticket intent",
|
|
17996
18628
|
"acceptance criteria",
|
|
@@ -18026,6 +18658,7 @@ function loopProofContract(recipe, options2, deliveryMode) {
|
|
|
18026
18658
|
const isTicketWriter = mode === "ticket_writer";
|
|
18027
18659
|
const needsRuntimeEvidence = mode === "runtime_fix";
|
|
18028
18660
|
const needsWorkItem = mode === "ticket_fix" || options2.needsWorkItem;
|
|
18661
|
+
const isWebDogfood = recipe.id === "web_dogfood";
|
|
18029
18662
|
const isSecurityDependency = recipe.id === "security_dependency";
|
|
18030
18663
|
const isSecurityFinding = recipe.id === "security_finding";
|
|
18031
18664
|
const isSecurityTesting = recipe.id === "security_testing";
|
|
@@ -18035,7 +18668,12 @@ function loopProofContract(recipe, options2, deliveryMode) {
|
|
|
18035
18668
|
"map_proof",
|
|
18036
18669
|
"team_rules_applied",
|
|
18037
18670
|
"mission_receipt",
|
|
18038
|
-
...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
|
+
] : [
|
|
18039
18677
|
"acceptance_criteria_evidence",
|
|
18040
18678
|
"validation_evidence",
|
|
18041
18679
|
"review_readiness",
|
|
@@ -18064,7 +18702,8 @@ function loopProofContract(recipe, options2, deliveryMode) {
|
|
|
18064
18702
|
]),
|
|
18065
18703
|
requiredReceipts: unique([
|
|
18066
18704
|
"mission_plan",
|
|
18067
|
-
...isTicketWriter ? [] : ["review", "handoff"],
|
|
18705
|
+
...isTicketWriter || isWebDogfood ? [] : ["review", "handoff"],
|
|
18706
|
+
...isWebDogfood ? ["handoff"] : [],
|
|
18068
18707
|
"receipt_ledger"
|
|
18069
18708
|
]),
|
|
18070
18709
|
readyStatuses: {
|
|
@@ -18078,6 +18717,11 @@ function loopProofContract(recipe, options2, deliveryMode) {
|
|
|
18078
18717
|
"Human judgment, acceptance criteria, non-goals, and owner boundaries are explicit.",
|
|
18079
18718
|
"Machine-readable run fields and Brief evidence contract are present.",
|
|
18080
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."
|
|
18081
18725
|
] : [
|
|
18082
18726
|
"Runtime evidence packet is ready when runtime evidence is part of the mission.",
|
|
18083
18727
|
...isSecurityDependency ? [
|
|
@@ -18164,6 +18808,11 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
|
|
|
18164
18808
|
"generatedArtifacts.contentProofPack.readiness",
|
|
18165
18809
|
"generatedArtifacts.productOperatingContext"
|
|
18166
18810
|
] : [],
|
|
18811
|
+
...recipe.id === "web_dogfood" ? [
|
|
18812
|
+
"generatedArtifacts.browserJourneyEvidence",
|
|
18813
|
+
"generatedArtifacts.dogfoodingReport",
|
|
18814
|
+
"generatedArtifacts.dogfoodingReport.topFindings[]"
|
|
18815
|
+
] : [],
|
|
18167
18816
|
"runContract.exitCodes[]"
|
|
18168
18817
|
],
|
|
18169
18818
|
artifacts,
|
|
@@ -18172,8 +18821,8 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
|
|
|
18172
18821
|
proofContract,
|
|
18173
18822
|
successCriteria: [
|
|
18174
18823
|
"The context receipt cites the rules, source paths, tests, owners, and memories used by the agent.",
|
|
18175
|
-
recipe.id === "ticket_write" ? "The ticket separates human judgment from execution instructions and names unresolved decisions before automation starts." : recipe.id === "security_dependency" ? "The dependency vulnerability packet names advisory id, package, affected range, fixed range, manifest, lockfile, direct/transitive path, reachability, remediation choice, and rollback." : recipe.id === "security_finding" ? "The security finding packet names alert source, rule id, classification, source-to-sink or trust boundary, affected users/data, owner, exploitability, and validation path." : recipe.id === "security_testing" ? "The security test plan names the protected property, bad case, safe case, command, severity threshold, false-positive policy, owner, privacy boundary, and rollout mode." : recipe.id === "internal_app" ? "The app contract names the internal route, data source, auth context, allowed roles, row filters, audit events, and redaction rules before implementation." : recipe.id === "stale_pr" ? "The PR intent packet names original scope, unresolved review feedback, stale assumptions, validation, and handoff expectation." : recipe.id === "merge_conflict" ? "The conflict packet names conflicted files, both sides' intent, semantic conflicts, and validation before resolution." : recipe.id === "flaky_test" ? "The CI evidence packet names failing job or test, sample failure, platform, seed or timing signal, hypothesis, and validation path." : recipe.id === "small_refactor" ? "The mechanical scope packet names exact pattern, allowed files, exclusions, and expected behavior preservation." : recipe.id === "bug_investigation" ? "The hypothesis ledger names observed facts, prior attempts, plausible causes, disproven causes, next probes, and confidence." : recipe.id === "qa_edge_cases" ? "The edge-case inventory names current coverage, missing boundary cases, risky inputs, and validation commands." : recipe.id === "performance" ? "The benchmark packet names workload, command, metric, baseline, variance, success threshold, and rollback signal before edits." : recipe.id === "porting" ? "The source behavior packet names source paths, target paths, incompatible assumptions, owner decision, validation, and attribution notes." : recipe.id === "experiment" ? "The experiment contract names the hypothesis, success signal, throwaway boundary, rollback or discard path, and decision owner." : recipe.id === "session_review" ? "The session review uses receipt-derived activity and marks any need for raw prompts, logs, or transcripts explicitly." : recipe.id === "session_library" ? "The session library classifies every reusable example by proof quality, privacy posture, owner approval, and reuse trigger." : options2.needsRuntimeEvidence ? "The runtime evidence packet names the observed failure source, identifier, environment, time window, symptom, and open gaps." : options2.needsWorkItem ? "The work item packet names source, id, acceptance criteria, non-goals, and the update expected after execution." : recipe.id === "research" ? "The answer names sources, risks, and next steps without editing the repo." : "Required validation evidence is attached or the missing environment is explicitly explained.",
|
|
18176
|
-
recipe.id === "ticket_write" ? "Acceptance criteria, constraints, validation plan, review bar, stop conditions, and update template are parseable from the ticket body." : recipe.id === "security_dependency" ? "Install integrity, build/typecheck, targeted tests, and scanner/audit evidence show the vulnerability is remediated without breaking the dependency path." : recipe.id === "security_finding" ? "The root cause is fixed with negative regression, safe abuse-case proof, or scanner rerun evidence; suppression-only fixes are explicitly rejected or owner-approved." : recipe.id === "security_testing" ? "The added test or scanner catches the intended bad case or has a clear preventive rationale, and rollout mode is calibrated for developer workflow." : recipe.id === "internal_app" ? "The built app follows repo-native UI/data patterns and validation proves the access-control contract." : recipe.id === "stale_pr" ? "The handoff names review feedback addressed, validation, remaining risk, and next reviewer action." : recipe.id === "merge_conflict" ? "The resolution preserves intended behavior from both sides or clearly escalates unresolved semantic conflicts." : recipe.id === "flaky_test" ? "The result stabilizes the test or explains the product root cause with evidence rather than hiding the signal." : recipe.id === "small_refactor" ? "The diff stays mechanical and validation proves no intended behavior changed." : recipe.id === "bug_investigation" ? "The root cause, fix, no-fix decision, or escalation is tied to evidence and includes a follow-up watch signal." : recipe.id === "qa_edge_cases" ? "The added or improved tests cover meaningful behavior risk and do not mute, skip, or obscure the signal." : recipe.id === "performance" ? "Before/after measurements, tradeoffs, and rollback signals are recorded before the optimization is accepted." : recipe.id === "porting" ? "The target behavior preserves source intent, attribution is handled, and intentional differences are explicit." : recipe.id === "experiment" ? "The keep, discard, or formalize decision is recorded with validation and remaining production-readiness gaps." : recipe.id === "session_review" ? "Run cards or skill drafts are tied to actual receipts, validation quality, review outcomes, and privacy posture." : recipe.id === "session_library" ? "The
|
|
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.",
|
|
18177
18826
|
autonomy === "read_only" ? "No repo files are changed." : "The local diff has no blocker or high Brief findings before human review.",
|
|
18178
18827
|
"Useful repeated feedback is saved or intentionally skipped with a reason."
|
|
18179
18828
|
],
|
|
@@ -18221,6 +18870,12 @@ function missionRunContract(autonomy, recipe, validation, options2, deliveryMode
|
|
|
18221
18870
|
"The proposed tests do not map to observable behavior risk or only assert implementation details.",
|
|
18222
18871
|
"The change hides failures by muting, skipping, or weakening validation."
|
|
18223
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
|
+
] : [],
|
|
18224
18879
|
...recipe.id === "performance" ? [
|
|
18225
18880
|
"The benchmark, workload, baseline, variance, or success threshold is missing.",
|
|
18226
18881
|
"The optimization makes readability, correctness, or operability worse without an explicit human tradeoff."
|
|
@@ -18373,6 +19028,30 @@ function missionArtifacts(recipe, autonomy, options2) {
|
|
|
18373
19028
|
description: "Labels, helper text, descriptions, errors, announcements, and screen-reader context remain accurate after copy changes."
|
|
18374
19029
|
});
|
|
18375
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
|
+
}
|
|
18376
19055
|
if (recipe.id === "internal_app") {
|
|
18377
19056
|
artifacts.push({
|
|
18378
19057
|
id: "product_operating_context",
|
|
@@ -18841,6 +19520,30 @@ function missionHookEvents(recipe, autonomy, options2) {
|
|
|
18841
19520
|
mode: "advisory"
|
|
18842
19521
|
});
|
|
18843
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
|
+
}
|
|
18844
19547
|
if (recipe.id === "ticket_write") {
|
|
18845
19548
|
events.push({
|
|
18846
19549
|
id: "before_ticket_execution",
|
|
@@ -19012,10 +19715,10 @@ function missionHookEvents(recipe, autonomy, options2) {
|
|
|
19012
19715
|
if (recipe.id === "session_library") {
|
|
19013
19716
|
events.push({
|
|
19014
19717
|
id: "before_session_library_publish",
|
|
19015
|
-
label: "Before
|
|
19718
|
+
label: "Before team sharing",
|
|
19016
19719
|
timing: "before",
|
|
19017
19720
|
trigger: "An agent turns previous sessions, transcripts, prompts, or run cards into shared examples.",
|
|
19018
|
-
action: "Require receipt proof, privacy classification, owner approval, and explicit raw-import approval before
|
|
19721
|
+
action: "Require receipt proof, privacy classification, owner approval, and explicit raw-import approval before sharing examples with the team.",
|
|
19019
19722
|
mode: "blocking"
|
|
19020
19723
|
});
|
|
19021
19724
|
}
|
|
@@ -19472,6 +20175,31 @@ var init_missionPlan = __esm({
|
|
|
19472
20175
|
"learn"
|
|
19473
20176
|
]
|
|
19474
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
|
+
},
|
|
19475
20203
|
{
|
|
19476
20204
|
id: "performance",
|
|
19477
20205
|
title: "Performance loop",
|
|
@@ -20106,6 +20834,51 @@ var init_missionPlan = __esm({
|
|
|
20106
20834
|
"Signal quality improved rather than only coverage count."
|
|
20107
20835
|
]
|
|
20108
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
|
+
}),
|
|
20109
20882
|
performance: workPattern({
|
|
20110
20883
|
title: "Performance work pattern",
|
|
20111
20884
|
teamValue: "Requires a baseline, workload, threshold, measured diff, and rollback signal before optimization work expands.",
|
|
@@ -20251,7 +21024,7 @@ var init_missionPlan = __esm({
|
|
|
20251
21024
|
guidance: [
|
|
20252
21025
|
"Keep one curator responsible for privacy classification and final library shape.",
|
|
20253
21026
|
"Use parallel agents only to summarize candidate receipts or draft examples from approved sources.",
|
|
20254
|
-
"Do not
|
|
21027
|
+
"Do not share raw transcripts, secrets, logs, or customer data without explicit approval."
|
|
20255
21028
|
]
|
|
20256
21029
|
}
|
|
20257
21030
|
}),
|
|
@@ -20416,7 +21189,7 @@ function categoryForRecipe(recipeId) {
|
|
|
20416
21189
|
if (recipeId === "incident_fix" || recipeId === "bug_investigation" || recipeId === "flaky_test") {
|
|
20417
21190
|
return "runtime";
|
|
20418
21191
|
}
|
|
20419
|
-
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") {
|
|
20420
21193
|
return "quality";
|
|
20421
21194
|
}
|
|
20422
21195
|
if (recipeId === "internal_app")
|
|
@@ -20437,6 +21210,8 @@ function toolForRecipe(recipeId) {
|
|
|
20437
21210
|
return "brief.write_ticket";
|
|
20438
21211
|
if (recipeId === "ticket_fix")
|
|
20439
21212
|
return "brief.fix_ticket";
|
|
21213
|
+
if (recipeId === "web_dogfood")
|
|
21214
|
+
return "browser + brief.save_handoff";
|
|
20440
21215
|
if (recipeId === "incident_fix" || recipeId === "flaky_test") {
|
|
20441
21216
|
return "brief.runtime_fix";
|
|
20442
21217
|
}
|
|
@@ -20527,6 +21302,17 @@ function requiredInputsForRecipe(recipeId) {
|
|
|
20527
21302
|
"privacy boundary for source, logs, secrets, and customer data"
|
|
20528
21303
|
];
|
|
20529
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
|
+
}
|
|
20530
21316
|
if (recipeId === "incident_fix" || recipeId === "flaky_test" || recipeId === "bug_investigation") {
|
|
20531
21317
|
return [
|
|
20532
21318
|
"observed failure source",
|
|
@@ -20659,6 +21445,17 @@ function proofBarForRecipe(recipeId) {
|
|
|
20659
21445
|
"the new signal is saved as team context for future missions"
|
|
20660
21446
|
];
|
|
20661
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
|
+
}
|
|
20662
21459
|
if (recipeId === "incident_fix" || recipeId === "bug_investigation") {
|
|
20663
21460
|
return [
|
|
20664
21461
|
"observed facts",
|
|
@@ -20758,7 +21555,7 @@ function privacyBoundaryForRecipe(recipeId) {
|
|
|
20758
21555
|
if (recipeId === "security_finding") {
|
|
20759
21556
|
return [
|
|
20760
21557
|
"redact raw secrets, tokens, cookies, customer payloads, and private logs before agent use",
|
|
20761
|
-
"do not
|
|
21558
|
+
"do not share exploit strings or abuse-case details outside the approved repo/security channel",
|
|
20762
21559
|
"provider rotation or invalidation status is tracked without exposing the replacement credential",
|
|
20763
21560
|
"false-positive and accepted-risk decisions need named owner approval and audit trail"
|
|
20764
21561
|
];
|
|
@@ -20771,6 +21568,14 @@ function privacyBoundaryForRecipe(recipeId) {
|
|
|
20771
21568
|
"keep scanner findings and test fixtures free of real credentials and production payloads"
|
|
20772
21569
|
];
|
|
20773
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
|
+
}
|
|
20774
21579
|
if (recipeId === "incident_fix" || recipeId === "flaky_test" || recipeId === "bug_investigation") {
|
|
20775
21580
|
return [
|
|
20776
21581
|
"summarize raw logs into observed facts first",
|
|
@@ -20789,7 +21594,7 @@ function privacyBoundaryForRecipe(recipeId) {
|
|
|
20789
21594
|
return [
|
|
20790
21595
|
"derive examples from receipts before raw transcripts",
|
|
20791
21596
|
"require approval for prompts, logs, secrets, or customer data",
|
|
20792
|
-
"
|
|
21597
|
+
"share concise run cards, not private session dumps"
|
|
20793
21598
|
];
|
|
20794
21599
|
}
|
|
20795
21600
|
return [
|
|
@@ -20822,6 +21627,9 @@ function teamOutcomeForRecipe(recipeId) {
|
|
|
20822
21627
|
if (recipeId === "security_testing") {
|
|
20823
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.";
|
|
20824
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
|
+
}
|
|
20825
21633
|
if (recipeId === "flaky_test") {
|
|
20826
21634
|
return "Teams reduce CI noise without normalizing muted, skipped, or blindly retried failures.";
|
|
20827
21635
|
}
|
|
@@ -20862,6 +21670,9 @@ function firstRunPrompts(items) {
|
|
|
20862
21670
|
if (item.recipeId === "security_testing") {
|
|
20863
21671
|
return 'Call brief.mission_plan with recipe "security_testing" for the security test, scanner, dependency-review, or CI gate you want to add.';
|
|
20864
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
|
+
}
|
|
20865
21676
|
return `Call brief.mission_plan with recipe "${item.recipeId}" for: <describe the work>.`;
|
|
20866
21677
|
});
|
|
20867
21678
|
}
|
|
@@ -20919,7 +21730,7 @@ function querySynonyms(term) {
|
|
|
20919
21730
|
return ["mcp", "client", "surface", "terminal", "ide"];
|
|
20920
21731
|
}
|
|
20921
21732
|
if (term === "dogfood" || term === "dogfooding") {
|
|
20922
|
-
return ["
|
|
21733
|
+
return ["web", "browser", "journey", "receipt", "review"];
|
|
20923
21734
|
}
|
|
20924
21735
|
if (term === "codex")
|
|
20925
21736
|
return ["codex", "terminal", "mcp"];
|
|
@@ -20963,6 +21774,9 @@ function queryScore(item, terms) {
|
|
|
20963
21774
|
if (termSet.has("quality") && (item.category === "quality" || item.recipeId === "pr_review")) {
|
|
20964
21775
|
score += 4;
|
|
20965
21776
|
}
|
|
21777
|
+
if ((termSet.has("quality") || termSet.has("review")) && item.recipeId === "pr_review") {
|
|
21778
|
+
score += 8;
|
|
21779
|
+
}
|
|
20966
21780
|
if (termSet.has("product") && item.recipeId === "feature") {
|
|
20967
21781
|
score += 4;
|
|
20968
21782
|
}
|
|
@@ -20981,8 +21795,8 @@ function queryScore(item, terms) {
|
|
|
20981
21795
|
if ((termSet.has("scanner") || termSet.has("scanning") || termSet.has("gate") || termSet.has("tests")) && item.recipeId === "security_testing") {
|
|
20982
21796
|
score += 7;
|
|
20983
21797
|
}
|
|
20984
|
-
if (termSet.has("dogfood") &&
|
|
20985
|
-
score +=
|
|
21798
|
+
if (termSet.has("dogfood") && item.recipeId === "web_dogfood") {
|
|
21799
|
+
score += 12;
|
|
20986
21800
|
}
|
|
20987
21801
|
if (termSet.has("tdd") && ["feature", "bugfix", "flaky_test", "dependency"].includes(item.recipeId)) {
|
|
20988
21802
|
score += 4;
|
|
@@ -21054,6 +21868,7 @@ var init_agentLoopCatalog = __esm({
|
|
|
21054
21868
|
"security_testing",
|
|
21055
21869
|
"flaky_test",
|
|
21056
21870
|
"bug_investigation",
|
|
21871
|
+
"web_dogfood",
|
|
21057
21872
|
"stale_pr",
|
|
21058
21873
|
"internal_app",
|
|
21059
21874
|
"pr_review",
|
|
@@ -24223,6 +25038,8 @@ function runCardTitle(recipeId) {
|
|
|
24223
25038
|
return "Repeat bug investigation loop";
|
|
24224
25039
|
case "qa_edge_cases":
|
|
24225
25040
|
return "Repeat QA edge-case loop";
|
|
25041
|
+
case "web_dogfood":
|
|
25042
|
+
return "Repeat web app dogfooding loop";
|
|
24226
25043
|
case "performance":
|
|
24227
25044
|
return "Repeat performance loop";
|
|
24228
25045
|
case "porting":
|
|
@@ -24271,6 +25088,8 @@ function runCardUseWhen(recipeId) {
|
|
|
24271
25088
|
return "Use when a hard, rare, intermittent, or previously failed bug needs hypotheses, evidence, validation, and a watch signal.";
|
|
24272
25089
|
case "qa_edge_cases":
|
|
24273
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.";
|
|
24274
25093
|
case "performance":
|
|
24275
25094
|
return "Use when latency, throughput, memory, CPU, or benchmark work needs a baseline before code changes.";
|
|
24276
25095
|
case "porting":
|
|
@@ -24386,6 +25205,16 @@ function runCardSteps(recipeId) {
|
|
|
24386
25205
|
"decision"
|
|
24387
25206
|
];
|
|
24388
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
|
+
}
|
|
24389
25218
|
if (recipeId === "session_review") {
|
|
24390
25219
|
return [
|
|
24391
25220
|
"receipts",
|
|
@@ -24504,6 +25333,9 @@ function runCardStopConditions(recipeId) {
|
|
|
24504
25333
|
if (recipeId === "experiment") {
|
|
24505
25334
|
stops.unshift("Success signal, throwaway boundary, discard path, or decision owner is missing.");
|
|
24506
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
|
+
}
|
|
24507
25339
|
if (recipeId === "session_review") {
|
|
24508
25340
|
return [
|
|
24509
25341
|
"Raw prompt transcripts, secrets, customer data, or private logs are required without explicit approval.",
|
|
@@ -24597,6 +25429,9 @@ function inferLoopId2(task) {
|
|
|
24597
25429
|
if (/\b(edge cases?|missing tests?|test gaps?|coverage gaps?|property[- ]based|qa|quality pass|corner cases?)\b/.test(normalized)) {
|
|
24598
25430
|
return "qa_edge_cases";
|
|
24599
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
|
+
}
|
|
24600
25435
|
if (/\b(performance|latency|throughput|benchmark|slow|speed|optimi[sz]e|profile|memory|cpu)\b/.test(normalized)) {
|
|
24601
25436
|
return "performance";
|
|
24602
25437
|
}
|
|
@@ -25264,6 +26099,7 @@ function autoEngageInstruction(scan, skillLearning) {
|
|
|
25264
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.",
|
|
25265
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.",
|
|
25266
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.",
|
|
25267
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.",
|
|
25268
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.",
|
|
25269
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.",
|
|
@@ -25347,6 +26183,14 @@ function autoEngageTriggers() {
|
|
|
25347
26183
|
agentBehavior: "Look for existing primitives and saved references before adding another implementation.",
|
|
25348
26184
|
proof: "The final update names reused candidates or explains why new code was justified."
|
|
25349
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
|
+
},
|
|
25350
26194
|
{
|
|
25351
26195
|
id: "scope_change",
|
|
25352
26196
|
label: "Scope changes",
|
|
@@ -26079,7 +26923,7 @@ function consoleLoopContract(scan) {
|
|
|
26079
26923
|
`Use this when the current console cannot call MCP tools directly for ${scan.repoName}.`,
|
|
26080
26924
|
"",
|
|
26081
26925
|
"1. Ask the operator for a Brief context packet or to run `brief.context_for_task` for the task.",
|
|
26082
|
-
"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.",
|
|
26083
26927
|
"3. Keep scope bounded to the cited files, owners, rules, tests, and wiki pages.",
|
|
26084
26928
|
"4. Before suggesting handoff, list the validation commands to run and what each proves.",
|
|
26085
26929
|
"5. Ask for `brief.review_diff`, `brief.save_handoff`, and `brief.verify_receipts` from an MCP-capable surface before PR review.",
|
|
@@ -26121,11 +26965,12 @@ function firstRunPromptsFor(label, scan) {
|
|
|
26121
26965
|
"Ask the agent: Save one proven implementation reference with brief.save_reference, then list it with brief.reference_implementations.",
|
|
26122
26966
|
"Ask the agent: Call brief.agent_pack_inventory and summarize skills, commands, hooks, MCP configs, host configs, installers, and high-risk trust boundaries.",
|
|
26123
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.',
|
|
26124
26969
|
"For UI work: Call brief.design_system_map and use the team's templates, tokens, components, setup rules, and design_adherence gate.",
|
|
26125
26970
|
"For a ticket: Call brief.fix_ticket with the work item, optional runtime evidence, and autonomy validate.",
|
|
26126
26971
|
"For a runtime signal: Call brief.runtime_fix with the Sentry issue, Datadog trace, CI failure, log query, or incident id.",
|
|
26127
26972
|
'For an internal analytics app: Call brief.mission_plan with recipe "internal_app", then follow the returned auth/data/access-control gates.',
|
|
26128
|
-
'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.',
|
|
26129
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."
|
|
26130
26975
|
];
|
|
26131
26976
|
}
|
|
@@ -26135,7 +26980,7 @@ function mcpJsonSnippet(installMode, briefSourceRoot, repoRoot) {
|
|
|
26135
26980
|
args: ["-lc", localSourceMcpCommand(briefSourceRoot, repoRoot)]
|
|
26136
26981
|
} : {
|
|
26137
26982
|
command: "npx",
|
|
26138
|
-
args: ["-y",
|
|
26983
|
+
args: ["-y", briefPublishedPackageInstallSpec]
|
|
26139
26984
|
};
|
|
26140
26985
|
const env = {
|
|
26141
26986
|
BRIEF_REPO_ROOT: repoRoot ?? "${workspaceFolder}",
|
|
@@ -26202,7 +27047,7 @@ function rolloutStepsFor(readiness) {
|
|
|
26202
27047
|
"Promote the repo once rules, tests, owners, and memories are discoverable."
|
|
26203
27048
|
];
|
|
26204
27049
|
}
|
|
26205
|
-
var ALL_TARGETS, briefPublishedPackageSpec, TARGET_LABELS, INSTALL_COMMANDS, CONNECTOR_DESTINATIONS;
|
|
27050
|
+
var ALL_TARGETS, briefPublishedPackageVersion, briefPublishedPackageSpec, briefPublishedPackageInstallSpec, TARGET_LABELS, INSTALL_COMMANDS, CONNECTOR_DESTINATIONS;
|
|
26206
27051
|
var init_agentSetup = __esm({
|
|
26207
27052
|
"../core/dist/agentSetup.js"() {
|
|
26208
27053
|
"use strict";
|
|
@@ -26220,7 +27065,9 @@ var init_agentSetup = __esm({
|
|
|
26220
27065
|
"ide",
|
|
26221
27066
|
"generic"
|
|
26222
27067
|
];
|
|
26223
|
-
|
|
27068
|
+
briefPublishedPackageVersion = "0.1.4";
|
|
27069
|
+
briefPublishedPackageSpec = `runbrief@${briefPublishedPackageVersion}`;
|
|
27070
|
+
briefPublishedPackageInstallSpec = `runbrief@^${briefPublishedPackageVersion}`;
|
|
26224
27071
|
TARGET_LABELS = {
|
|
26225
27072
|
terminal: "Terminal agent",
|
|
26226
27073
|
vscode: "VS Code",
|
|
@@ -26233,15 +27080,15 @@ var init_agentSetup = __esm({
|
|
|
26233
27080
|
generic: "Any MCP client"
|
|
26234
27081
|
};
|
|
26235
27082
|
INSTALL_COMMANDS = {
|
|
26236
|
-
terminal: `npx -y ${
|
|
26237
|
-
vscode: `npx -y ${
|
|
26238
|
-
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}`,
|
|
26239
27086
|
claude_console: "Paste the mission_plan loop recipe when direct MCP tools are unavailable.",
|
|
26240
|
-
codex: `codex mcp add brief -- npx -y ${
|
|
26241
|
-
cursor: `npx -y ${
|
|
27087
|
+
codex: `codex mcp add brief -- npx -y ${briefPublishedPackageInstallSpec}`,
|
|
27088
|
+
cursor: `npx -y ${briefPublishedPackageInstallSpec}`,
|
|
26242
27089
|
cursor_console: "Paste the mission_plan loop recipe when direct MCP tools are unavailable.",
|
|
26243
|
-
ide: `npx -y ${
|
|
26244
|
-
generic: `npx -y ${
|
|
27090
|
+
ide: `npx -y ${briefPublishedPackageInstallSpec}`,
|
|
27091
|
+
generic: `npx -y ${briefPublishedPackageInstallSpec}`
|
|
26245
27092
|
};
|
|
26246
27093
|
CONNECTOR_DESTINATIONS = {
|
|
26247
27094
|
terminal: "Repo shell MCP server env",
|
|
@@ -26511,6 +27358,9 @@ function recipeForIntent(kind, request, explicitRecipe, runtimeEvidence) {
|
|
|
26511
27358
|
return /\b(bug|broken|fix|incorrect|wrong)\b/i.test(request) ? "bugfix" : "feature";
|
|
26512
27359
|
}
|
|
26513
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
|
+
}
|
|
26514
27364
|
if (isDesignLedChange(lower)) {
|
|
26515
27365
|
return "design_change";
|
|
26516
27366
|
}
|
|
@@ -26547,7 +27397,7 @@ function isProductJourneyAudit(request) {
|
|
|
26547
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);
|
|
26548
27398
|
}
|
|
26549
27399
|
function autonomyForIntent(kind, recipe) {
|
|
26550
|
-
if (kind === "context_for_task" || kind === "write_ticket" || recipe === "research") {
|
|
27400
|
+
if (kind === "context_for_task" || kind === "write_ticket" || recipe === "research" || recipe === "web_dogfood") {
|
|
26551
27401
|
return "read_only";
|
|
26552
27402
|
}
|
|
26553
27403
|
return "validate";
|
|
@@ -27208,6 +28058,9 @@ function summaryForIntent(kind, recipe, workItem, runtimeEvidence) {
|
|
|
27208
28058
|
return "Brief inferred a compact pre-PR readiness pass.";
|
|
27209
28059
|
if (kind === "context_for_task")
|
|
27210
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
|
+
}
|
|
27211
28064
|
return `Brief inferred a ${recipe ?? "mission"} loop.`;
|
|
27212
28065
|
}
|
|
27213
28066
|
function reasonForIntent(kind, request, workItem, runtimeEvidence) {
|
|
@@ -27281,7 +28134,7 @@ function summarizeBeforeYouCodeSharedContext(delivery) {
|
|
|
27281
28134
|
next: "Shared guidance is not available in this session; keep new learning personal until the workspace connection is ready."
|
|
27282
28135
|
};
|
|
27283
28136
|
}
|
|
27284
|
-
const next = delivery.status === "applied" ? "Use the applied shared guidance, then save a focused learning or reference when the pass proves a reusable pattern." : delivery.status === "available_not_applied" ? "Shared guidance exists but did not match this task; inspect the relevant team item before creating a new pattern." : "No approved shared guidance exists yet; keep this pass personal and
|
|
28137
|
+
const next = delivery.status === "applied" ? "Use the applied shared guidance, then save a focused learning or reference when the pass proves a reusable pattern." : delivery.status === "available_not_applied" ? "Shared guidance exists but did not match this task; inspect the relevant team item before creating a new pattern." : "No approved shared guidance exists yet; keep this pass personal and make it available to the team only after the pattern is proven.";
|
|
27285
28138
|
return {
|
|
27286
28139
|
status: delivery.status,
|
|
27287
28140
|
workspaceId: delivery.workspaceId,
|
|
@@ -27424,11 +28277,13 @@ function buildBeforeYouCodeMap(scan, input) {
|
|
|
27424
28277
|
files,
|
|
27425
28278
|
maxLines: 18
|
|
27426
28279
|
});
|
|
28280
|
+
const mapCoverage = mapCoverageFor2(scan);
|
|
27427
28281
|
const stopSignals2 = unique([
|
|
27428
28282
|
"Wrong repo, branch, or worktree.",
|
|
27429
28283
|
...target.branch?.startsWith("detached@") ? ["Detached HEAD; select the intended feature branch before editing."] : [],
|
|
27430
28284
|
...insights.agentPacket.stopSignals,
|
|
27431
28285
|
...productOperatingContext.stopSignals.slice(0, 3),
|
|
28286
|
+
...mapCoverage.status === "partial" ? [mapCoverage.summary] : [],
|
|
27432
28287
|
...target.dirty && files.length === 0 ? ["Dirty worktree is broad; classify baseline before editing."] : []
|
|
27433
28288
|
]).slice(0, 5);
|
|
27434
28289
|
const status = inspectFirst.length === 0 && reuseCandidates.length === 0 ? "needs_narrower_scope" : "ready";
|
|
@@ -27448,6 +28303,7 @@ function buildBeforeYouCodeMap(scan, input) {
|
|
|
27448
28303
|
reuseCandidates,
|
|
27449
28304
|
likelyTests,
|
|
27450
28305
|
productOperatingContext,
|
|
28306
|
+
mapCoverage,
|
|
27451
28307
|
...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
|
|
27452
28308
|
referenceFirst,
|
|
27453
28309
|
validationChecklist,
|
|
@@ -27460,6 +28316,7 @@ function buildBeforeYouCodeMap(scan, input) {
|
|
|
27460
28316
|
reuseCandidates,
|
|
27461
28317
|
likelyTests,
|
|
27462
28318
|
productOperatingContext,
|
|
28319
|
+
mapCoverage,
|
|
27463
28320
|
validationChecklist,
|
|
27464
28321
|
stopSignals: stopSignals2,
|
|
27465
28322
|
...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
|
|
@@ -27479,7 +28336,9 @@ function buildBeforeYouCodeMap(scan, input) {
|
|
|
27479
28336
|
candidates: reuseCandidates,
|
|
27480
28337
|
decision: reuseCandidates.length > 0 ? "For each candidate, choose reuse, adapt, or rule out and record the decision before creating a new primitive." : "No reuse candidate is grounded yet; run one focused search before creating a new primitive."
|
|
27481
28338
|
},
|
|
28339
|
+
mapCoverage,
|
|
27482
28340
|
...context.teamGuidance ? { teamGuidance: context.teamGuidance } : {},
|
|
28341
|
+
rules: context.rules.slice(0, 8),
|
|
27483
28342
|
referenceFirst,
|
|
27484
28343
|
likelyTests,
|
|
27485
28344
|
exactTests,
|
|
@@ -27491,6 +28350,19 @@ function buildBeforeYouCodeMap(scan, input) {
|
|
|
27491
28350
|
next: status === "ready" ? "Inspect these files and reuse candidates before editing; cite the reuse decision in the final handoff." : "Run brief.codebase_map or brief.context_for_task with a narrower task, folder, or file list before editing."
|
|
27492
28351
|
};
|
|
27493
28352
|
}
|
|
28353
|
+
function mapCoverageFor2(scan) {
|
|
28354
|
+
const mappedNodes = scan.stats.mapNodeCount;
|
|
28355
|
+
const omittedNodes = Math.max(0, scan.stats.mapOmittedCount ?? 0);
|
|
28356
|
+
const candidateNodes = Math.max(mappedNodes, scan.stats.mapTotalCandidateCount ?? mappedNodes + omittedNodes);
|
|
28357
|
+
const partial = Boolean(scan.stats.mapPartial) || omittedNodes > 0;
|
|
28358
|
+
return {
|
|
28359
|
+
status: partial ? "partial" : "complete",
|
|
28360
|
+
mappedNodes,
|
|
28361
|
+
candidateNodes,
|
|
28362
|
+
omittedNodes,
|
|
28363
|
+
summary: partial ? `Code map is partial: ${mappedNodes} of ${candidateNodes} candidate nodes are available; narrow by feature, folder, or symbol before treating a missing reuse candidate as proof none exists.` : `Code map covers all ${mappedNodes} indexed candidate nodes for this repo.`
|
|
28364
|
+
};
|
|
28365
|
+
}
|
|
27494
28366
|
function reuseStatusFor(candidates) {
|
|
27495
28367
|
if (candidates.length === 0)
|
|
27496
28368
|
return "missing";
|
|
@@ -27611,6 +28483,7 @@ function cockpitFor(input) {
|
|
|
27611
28483
|
"Broad e2e or full-suite tests unless routing, auth, persistence, or cross-page state changed."
|
|
27612
28484
|
];
|
|
27613
28485
|
const stopSignals2 = unique([
|
|
28486
|
+
...input.mapCoverage.status === "partial" ? [input.mapCoverage.summary] : [],
|
|
27614
28487
|
...input.stopSignals.slice(0, 3),
|
|
27615
28488
|
...mode === "lightweight" ? [
|
|
27616
28489
|
"The tiny pass starts touching behavior, state, data, auth, or multiple surfaces."
|
|
@@ -27798,55 +28671,55 @@ function diversifyJourneyStages(ranked, task) {
|
|
|
27798
28671
|
];
|
|
27799
28672
|
}
|
|
27800
28673
|
function journeyStagePathPreference(stage, filePath) {
|
|
27801
|
-
const
|
|
28674
|
+
const path21 = filePath.toLowerCase();
|
|
27802
28675
|
if (stage === "acquisition") {
|
|
27803
|
-
if (/landinginteractive\.[cm]?[tj]sx?$/.test(
|
|
28676
|
+
if (/landinginteractive\.[cm]?[tj]sx?$/.test(path21))
|
|
27804
28677
|
return 40;
|
|
27805
|
-
if (/apps\/web\/src\/app\/page\.[cm]?[tj]sx?$/.test(
|
|
28678
|
+
if (/apps\/web\/src\/app\/page\.[cm]?[tj]sx?$/.test(path21))
|
|
27806
28679
|
return 35;
|
|
27807
28680
|
}
|
|
27808
28681
|
if (stage === "access") {
|
|
27809
|
-
if (/apps\/web\/src\/app\/login\/page\.[cm]?[tj]sx?$/.test(
|
|
28682
|
+
if (/apps\/web\/src\/app\/login\/page\.[cm]?[tj]sx?$/.test(path21))
|
|
27810
28683
|
return 40;
|
|
27811
|
-
if (/api\/auth\/magic-link\/route\.[cm]?[tj]s$/.test(
|
|
28684
|
+
if (/api\/auth\/magic-link\/route\.[cm]?[tj]s$/.test(path21))
|
|
27812
28685
|
return 35;
|
|
27813
28686
|
}
|
|
27814
28687
|
if (stage === "workspace") {
|
|
27815
|
-
if (/api\/v1\/workspaces\/bootstrap\/route\.[cm]?[tj]s$/.test(
|
|
28688
|
+
if (/api\/v1\/workspaces\/bootstrap\/route\.[cm]?[tj]s$/.test(path21)) {
|
|
27816
28689
|
return 40;
|
|
27817
28690
|
}
|
|
27818
28691
|
}
|
|
27819
28692
|
if (stage === "activation") {
|
|
27820
|
-
if (/api\/v1\/workspaces\/[^/]+\/api-keys\/route\.[cm]?[tj]s$/.test(
|
|
28693
|
+
if (/api\/v1\/workspaces\/[^/]+\/api-keys\/route\.[cm]?[tj]s$/.test(path21)) {
|
|
27821
28694
|
return 40;
|
|
27822
28695
|
}
|
|
27823
|
-
if (/workspace-agent-actions\.[cm]?[tj]s$/.test(
|
|
28696
|
+
if (/workspace-agent-actions\.[cm]?[tj]s$/.test(path21))
|
|
27824
28697
|
return 35;
|
|
27825
|
-
if (/agent-handoff-composer\.[cm]?[tj]s$/.test(
|
|
28698
|
+
if (/agent-handoff-composer\.[cm]?[tj]s$/.test(path21))
|
|
27826
28699
|
return 30;
|
|
27827
28700
|
}
|
|
27828
28701
|
if (stage === "collaboration") {
|
|
27829
|
-
if (/api\/v1\/workspaces\/[^/]+\/invitations\/route\.[cm]?[tj]s$/.test(
|
|
28702
|
+
if (/api\/v1\/workspaces\/[^/]+\/invitations\/route\.[cm]?[tj]s$/.test(path21)) {
|
|
27830
28703
|
return 40;
|
|
27831
28704
|
}
|
|
27832
|
-
if (/api\/v1\/workspaces\/[^/]+\/members\/[^/]+\/route/.test(
|
|
28705
|
+
if (/api\/v1\/workspaces\/[^/]+\/members\/[^/]+\/route/.test(path21)) {
|
|
27833
28706
|
return 30;
|
|
27834
28707
|
}
|
|
27835
28708
|
}
|
|
27836
28709
|
if (stage === "monetization") {
|
|
27837
|
-
if (/billing\/checkout\/route\.[cm]?[tj]s$/.test(
|
|
28710
|
+
if (/billing\/checkout\/route\.[cm]?[tj]s$/.test(path21))
|
|
27838
28711
|
return 40;
|
|
27839
|
-
if (/pricing\/page\.[cm]?[tj]sx?$/.test(
|
|
28712
|
+
if (/pricing\/page\.[cm]?[tj]sx?$/.test(path21))
|
|
27840
28713
|
return 35;
|
|
27841
28714
|
}
|
|
27842
28715
|
return 0;
|
|
27843
28716
|
}
|
|
27844
|
-
function beforeYouCodePathIntentScore(
|
|
27845
|
-
const lowerPath = (
|
|
28717
|
+
function beforeYouCodePathIntentScore(path21, task) {
|
|
28718
|
+
const lowerPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
|
|
27846
28719
|
const lowerTask = task.toLowerCase();
|
|
27847
|
-
let score = namedCodePathIntentScore(
|
|
28720
|
+
let score = namedCodePathIntentScore(path21, task);
|
|
27848
28721
|
const wantsInterface = /\b(app|design|header|interface|landing|layout|mobile|navigation|page|responsive|spacing|ui|ux|visual|workspace)\b/.test(lowerTask);
|
|
27849
|
-
score += lexicalPathTaskScore(
|
|
28722
|
+
score += lexicalPathTaskScore(path21, task);
|
|
27850
28723
|
const wantsLanding = /\b(hero|homepage|landing)\b/.test(lowerTask);
|
|
27851
28724
|
const wantsAppShell = /\b(app|header|internal|mobile|navigation|responsive|sidebar|workspace)\b/.test(lowerTask);
|
|
27852
28725
|
const wantsAuth = /\b(auth|authentication|callback|login|magic link|otp|session|sign[ -]?in|sign[ -]?out)\b/.test(lowerTask);
|
|
@@ -27996,8 +28869,8 @@ function beforeYouCodePathIntentScore(path20, task) {
|
|
|
27996
28869
|
}
|
|
27997
28870
|
return score;
|
|
27998
28871
|
}
|
|
27999
|
-
function beforeYouCodeIntentSummary(
|
|
28000
|
-
const normalizedPath = (
|
|
28872
|
+
function beforeYouCodeIntentSummary(path21, task) {
|
|
28873
|
+
const normalizedPath = (path21.split("#", 1)[0] ?? path21).toLowerCase();
|
|
28001
28874
|
if (!isInternalAppTask(task))
|
|
28002
28875
|
return void 0;
|
|
28003
28876
|
if (/apps\/web\/src\/app\/app\/page\.[cm]?[tj]sx?$/.test(normalizedPath)) {
|
|
@@ -28023,7 +28896,7 @@ function prefersPrimaryWorkspaceTask(task) {
|
|
|
28023
28896
|
return true;
|
|
28024
28897
|
return /\b(design|designer|figma|screenshot|visual|ui|ux|layout|spacing|typography|responsive|mobile)\b/i.test(task);
|
|
28025
28898
|
}
|
|
28026
|
-
function lexicalPathTaskScore(
|
|
28899
|
+
function lexicalPathTaskScore(path21, task) {
|
|
28027
28900
|
const ignored = /* @__PURE__ */ new Set([
|
|
28028
28901
|
"agent",
|
|
28029
28902
|
"brief",
|
|
@@ -28043,7 +28916,7 @@ function lexicalPathTaskScore(path20, task) {
|
|
|
28043
28916
|
taskWords.add("workspace");
|
|
28044
28917
|
taskWords.add("pane");
|
|
28045
28918
|
}
|
|
28046
|
-
const pathWords =
|
|
28919
|
+
const pathWords = path21.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 4 && !ignored.has(word));
|
|
28047
28920
|
const matches = unique(pathWords.filter((word) => taskWords.has(word)));
|
|
28048
28921
|
const structuralBonus = matches.reduce((score, word) => {
|
|
28049
28922
|
if (!["header", "pane", "shell", "sidebar", "workspace"].includes(word)) {
|
|
@@ -28117,6 +28990,7 @@ function compactTextFor2(input) {
|
|
|
28117
28990
|
`Before coding: ${input.headline}`,
|
|
28118
28991
|
`Target: ${input.target.repoName} | ${input.target.branch ?? "branch unknown"} | ${input.target.repoRoot}`,
|
|
28119
28992
|
`Worktree: ${input.target.dirty ? "dirty" : "clean"}${input.target.changedFiles.length ? `; changed ${input.target.changedFiles.slice(0, 4).join(", ")}` : ""}${input.target.untrackedFiles.length ? `; untracked ${input.target.untrackedFiles.slice(0, 3).join(", ")}` : ""}`,
|
|
28993
|
+
`Map coverage: ${input.mapCoverage.summary}`,
|
|
28120
28994
|
...input.target.requestedFiles.length ? [`Scope files: ${input.target.requestedFiles.slice(0, 5).join(", ")}`] : [],
|
|
28121
28995
|
...input.teamGuidance ? [
|
|
28122
28996
|
`Shared guidance: ${input.teamGuidance.applied.length > 0 ? input.teamGuidance.applied.slice(0, 2).map((item) => item.label).join(", ") : input.teamGuidance.status === "available_not_applied" ? "available, but none matched this task" : "none approved yet"}`
|
|
@@ -28167,16 +29041,20 @@ var init_agentBeforeYouCode = __esm({
|
|
|
28167
29041
|
// ../core/dist/agentWorkPacket.js
|
|
28168
29042
|
function buildAgentWorkPacket(input) {
|
|
28169
29043
|
const before = input.beforeYouCode;
|
|
28170
|
-
const rules = [
|
|
29044
|
+
const rules = uniqueRules2([
|
|
28171
29045
|
...(before.teamGuidance?.applied ?? []).map((item) => ({
|
|
28172
29046
|
rule: item.label,
|
|
28173
29047
|
source: `team:${item.kind}`
|
|
28174
29048
|
})),
|
|
29049
|
+
...(before.rules ?? []).map((item) => ({
|
|
29050
|
+
rule: compact(item.body || item.title, 180),
|
|
29051
|
+
source: item.sourcePaths[0] ?? "repo guidance"
|
|
29052
|
+
})),
|
|
28175
29053
|
...before.productOperatingContext.productRules.map((item) => ({
|
|
28176
29054
|
rule: item.rule,
|
|
28177
29055
|
source: item.source
|
|
28178
29056
|
}))
|
|
28179
|
-
].slice(0, 3);
|
|
29057
|
+
]).slice(0, 3);
|
|
28180
29058
|
const tests = before.exactTests.length > 0 ? before.exactTests.slice(0, 3).map((test) => ({
|
|
28181
29059
|
command: compact(test.command, 180),
|
|
28182
29060
|
reason: compact(test.reason, 180),
|
|
@@ -28192,7 +29070,8 @@ function buildAgentWorkPacket(input) {
|
|
|
28192
29070
|
]).slice(0, 3);
|
|
28193
29071
|
const stopConditions = unique2([
|
|
28194
29072
|
...before.stopSignals,
|
|
28195
|
-
...before.productOperatingContext.stopSignals
|
|
29073
|
+
...before.productOperatingContext.stopSignals,
|
|
29074
|
+
...before.mapCoverage.status === "partial" ? [before.mapCoverage.summary] : []
|
|
28196
29075
|
]).slice(0, 3);
|
|
28197
29076
|
const nextAction = input.nextAction ?? before.next ?? "Inspect the listed files and make the reuse decision before editing.";
|
|
28198
29077
|
const packet = {
|
|
@@ -28220,6 +29099,7 @@ function buildAgentWorkPacket(input) {
|
|
|
28220
29099
|
why: compact(item.why, 140)
|
|
28221
29100
|
}))
|
|
28222
29101
|
},
|
|
29102
|
+
mapCoverage: before.mapCoverage,
|
|
28223
29103
|
rules,
|
|
28224
29104
|
tests,
|
|
28225
29105
|
acceptance,
|
|
@@ -28236,6 +29116,7 @@ function formatAgentWorkPacket(packet) {
|
|
|
28236
29116
|
`Interpretation: ${packet.interpretation}`,
|
|
28237
29117
|
`Inspect first: ${inlineList(packet.inspectFirst.map((item) => `${item.path} - ${item.why}`))}`,
|
|
28238
29118
|
`Reuse (${packet.reuse.status}): ${packet.reuse.decision}; ${inlineList(packet.reuse.candidates.map((item) => `${item.path} - ${item.action}`))}`,
|
|
29119
|
+
`Map: ${packet.mapCoverage.summary}`,
|
|
28239
29120
|
`Rules: ${inlineList(packet.rules.map((item) => `${item.rule} [${item.source}]`))}`,
|
|
28240
29121
|
`Focused tests: ${inlineList(packet.tests.map((item) => `${item.command} - ${item.reason}`))}`,
|
|
28241
29122
|
`Acceptance: ${inlineList(packet.acceptance)}`,
|
|
@@ -28254,6 +29135,16 @@ function compact(value, maxLength) {
|
|
|
28254
29135
|
function unique2(values) {
|
|
28255
29136
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
28256
29137
|
}
|
|
29138
|
+
function uniqueRules2(rules) {
|
|
29139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29140
|
+
return rules.filter((item) => {
|
|
29141
|
+
const key = item.rule.toLowerCase();
|
|
29142
|
+
if (!item.rule || seen.has(key))
|
|
29143
|
+
return false;
|
|
29144
|
+
seen.add(key);
|
|
29145
|
+
return true;
|
|
29146
|
+
});
|
|
29147
|
+
}
|
|
28257
29148
|
var init_agentWorkPacket = __esm({
|
|
28258
29149
|
"../core/dist/agentWorkPacket.js"() {
|
|
28259
29150
|
"use strict";
|
|
@@ -28469,7 +29360,7 @@ var init_governance = __esm({
|
|
|
28469
29360
|
// ../core/dist/hookSetup.js
|
|
28470
29361
|
function generateHookSetup(input = {}) {
|
|
28471
29362
|
const mode = input.mode ?? "advisory";
|
|
28472
|
-
const packageCommand = input.packageCommand ?? `npx -y ${
|
|
29363
|
+
const packageCommand = input.packageCommand ?? `npx -y ${briefPublishedPackageInstallSpec}`;
|
|
28473
29364
|
const failOn = mode === "blocking" ? "high" : "blocker";
|
|
28474
29365
|
return {
|
|
28475
29366
|
mode,
|
|
@@ -31192,6 +32083,7 @@ var init_diffReview = __esm({
|
|
|
31192
32083
|
"handler",
|
|
31193
32084
|
"helpers",
|
|
31194
32085
|
"delete",
|
|
32086
|
+
"dynamic",
|
|
31195
32087
|
"get",
|
|
31196
32088
|
"head",
|
|
31197
32089
|
"input",
|
|
@@ -33076,6 +33968,7 @@ var init_dist2 = __esm({
|
|
|
33076
33968
|
init_agentBeforeYouCode();
|
|
33077
33969
|
init_agentWorkPacket();
|
|
33078
33970
|
init_productOperatingContext();
|
|
33971
|
+
init_productIntelligence();
|
|
33079
33972
|
init_ticketMissionRuntime();
|
|
33080
33973
|
init_governance();
|
|
33081
33974
|
init_hookSetup();
|
|
@@ -33102,7 +33995,7 @@ function cloudSyncStatus(env = process.env) {
|
|
|
33102
33995
|
return {
|
|
33103
33996
|
configured: false,
|
|
33104
33997
|
privacyMode,
|
|
33105
|
-
reason: privacyMode === "local-only" ? "Cloud sync is disabled by privacy mode." : "
|
|
33998
|
+
reason: privacyMode === "local-only" ? "Cloud sync is disabled by privacy mode." : "Connect Brief from the app to authorize this device, or provide BRIEF_API_URL and BRIEF_API_KEY for an approved legacy setup."
|
|
33106
33999
|
};
|
|
33107
34000
|
}
|
|
33108
34001
|
const status = {
|
|
@@ -33259,6 +34152,64 @@ async function fetchTeamContext(repoRoot, env = process.env, fetcher = fetch) {
|
|
|
33259
34152
|
});
|
|
33260
34153
|
return getTeamContextCloud(config, payload, fetcher);
|
|
33261
34154
|
}
|
|
34155
|
+
async function claimRemoteLoopRun(runnerId, leaseSeconds = 180, env = process.env, fetcher = fetch) {
|
|
34156
|
+
const config = cloudConfigFromEnv(env);
|
|
34157
|
+
if (!config) throw new Error("Brief cloud connection is not configured for the runner.");
|
|
34158
|
+
const payload = claimLoopRunRequestSchema.parse({ runnerId, leaseSeconds });
|
|
34159
|
+
const body = await loopRunRequest(
|
|
34160
|
+
config,
|
|
34161
|
+
"/api/v1/mcp/loop-runs/claim",
|
|
34162
|
+
"POST",
|
|
34163
|
+
payload,
|
|
34164
|
+
fetcher
|
|
34165
|
+
);
|
|
34166
|
+
if (!body.ok) throw new Error(body.message ?? "Brief could not claim a loop run.");
|
|
34167
|
+
if (!body.run) return null;
|
|
34168
|
+
const parsed = loopRunSummarySchema.safeParse(body.run);
|
|
34169
|
+
if (!parsed.success) throw new Error("Brief returned an invalid loop run.");
|
|
34170
|
+
return parsed.data;
|
|
34171
|
+
}
|
|
34172
|
+
async function updateRemoteLoopRun(runId, input, env = process.env, fetcher = fetch) {
|
|
34173
|
+
const config = cloudConfigFromEnv(env);
|
|
34174
|
+
if (!config) throw new Error("Brief cloud connection is not configured for the runner.");
|
|
34175
|
+
const payload = updateLoopRunRequestSchema.parse(input);
|
|
34176
|
+
const body = await loopRunRequest(
|
|
34177
|
+
config,
|
|
34178
|
+
`/api/v1/mcp/loop-runs/${encodeURIComponent(runId)}`,
|
|
34179
|
+
"PATCH",
|
|
34180
|
+
payload,
|
|
34181
|
+
fetcher
|
|
34182
|
+
);
|
|
34183
|
+
if (!body.ok) throw new Error(body.message ?? "Brief could not update the loop run.");
|
|
34184
|
+
const parsed = loopRunSummarySchema.safeParse(body.run);
|
|
34185
|
+
if (!parsed.success) throw new Error("Brief returned an invalid updated loop run.");
|
|
34186
|
+
return parsed.data;
|
|
34187
|
+
}
|
|
34188
|
+
async function loopRunRequest(config, route, method, payload, fetcher) {
|
|
34189
|
+
const controller = new AbortController();
|
|
34190
|
+
const timeout = setTimeout(() => controller.abort(), syncTimeoutMs);
|
|
34191
|
+
try {
|
|
34192
|
+
const response = await fetcher(`${config.apiUrl}${route}`, {
|
|
34193
|
+
method,
|
|
34194
|
+
headers: {
|
|
34195
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
34196
|
+
"content-type": "application/json"
|
|
34197
|
+
},
|
|
34198
|
+
body: JSON.stringify(payload),
|
|
34199
|
+
signal: controller.signal
|
|
34200
|
+
});
|
|
34201
|
+
const body = await safeJson(response);
|
|
34202
|
+
const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
34203
|
+
const result = {
|
|
34204
|
+
ok: response.ok && record.ok === true
|
|
34205
|
+
};
|
|
34206
|
+
if (typeof record.message === "string") result.message = record.message;
|
|
34207
|
+
if (record.run !== void 0) result.run = record.run;
|
|
34208
|
+
return result;
|
|
34209
|
+
} finally {
|
|
34210
|
+
clearTimeout(timeout);
|
|
34211
|
+
}
|
|
34212
|
+
}
|
|
33262
34213
|
function toRepoScanSummary(scan, privacyMode) {
|
|
33263
34214
|
return {
|
|
33264
34215
|
repoName: scan.repoName,
|
|
@@ -33304,7 +34255,7 @@ function skippedResult(env) {
|
|
|
33304
34255
|
return {
|
|
33305
34256
|
status: "skipped",
|
|
33306
34257
|
privacyMode,
|
|
33307
|
-
reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because
|
|
34258
|
+
reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because this device is not connected. Create a browser-authorized connection from the Brief app, then restart the agent."
|
|
33308
34259
|
};
|
|
33309
34260
|
}
|
|
33310
34261
|
function skippedTeamContextResult(env) {
|
|
@@ -33312,7 +34263,7 @@ function skippedTeamContextResult(env) {
|
|
|
33312
34263
|
return {
|
|
33313
34264
|
status: "skipped",
|
|
33314
34265
|
privacyMode,
|
|
33315
|
-
reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because
|
|
34266
|
+
reason: privacyMode === "local-only" ? "Cloud sync skipped because privacy mode is local-only." : "Cloud sync skipped because this device is not connected. Create a browser-authorized connection from the Brief app, then restart the agent."
|
|
33316
34267
|
};
|
|
33317
34268
|
}
|
|
33318
34269
|
function normalizeApiUrl(apiUrl) {
|
|
@@ -33736,9 +34687,9 @@ function issuesValue(value) {
|
|
|
33736
34687
|
if (typeof item === "string") return item;
|
|
33737
34688
|
if (!item || typeof item !== "object") return "";
|
|
33738
34689
|
const record = item;
|
|
33739
|
-
const
|
|
34690
|
+
const path21 = Array.isArray(record.path) ? record.path.join(".") : "";
|
|
33740
34691
|
const message = stringValue(record.message);
|
|
33741
|
-
return [
|
|
34692
|
+
return [path21, message].filter(Boolean).join(": ");
|
|
33742
34693
|
}).filter(Boolean).slice(0, 5).join("; ");
|
|
33743
34694
|
}
|
|
33744
34695
|
var syncTimeoutMs, canonicalBriefApiUrl, legacyBriefApiUrls;
|
|
@@ -34129,14 +35080,178 @@ var init_missionInputOptions = __esm({
|
|
|
34129
35080
|
}
|
|
34130
35081
|
});
|
|
34131
35082
|
|
|
34132
|
-
// ../mcp/src/
|
|
34133
|
-
import {
|
|
35083
|
+
// ../mcp/src/cloudCredentials.ts
|
|
35084
|
+
import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
35085
|
+
import os from "node:os";
|
|
34134
35086
|
import path14 from "node:path";
|
|
35087
|
+
async function bootstrapCloudCredentials(env = process.env) {
|
|
35088
|
+
if (env.BRIEF_PRIVACY_MODE === "local-only") {
|
|
35089
|
+
return { status: "skipped", reason: "local_only" };
|
|
35090
|
+
}
|
|
35091
|
+
if (env.BRIEF_API_URL && env.BRIEF_API_KEY) {
|
|
35092
|
+
return { status: "configured", source: "env" };
|
|
35093
|
+
}
|
|
35094
|
+
const credentialPath = credentialsPath(env);
|
|
35095
|
+
const requestedOrigin = originFrom(
|
|
35096
|
+
env.BRIEF_API_URL ?? env.BRIEF_CONNECT_URL
|
|
35097
|
+
);
|
|
35098
|
+
const requestedWorkspace = env.BRIEF_WORKSPACE_ID?.trim();
|
|
35099
|
+
const stored = readCredentialFile(credentialPath);
|
|
35100
|
+
const existing = stored.credentials.find((credential2) => {
|
|
35101
|
+
if (requestedOrigin && originFrom(credential2.apiUrl) !== requestedOrigin) {
|
|
35102
|
+
return false;
|
|
35103
|
+
}
|
|
35104
|
+
return !requestedWorkspace || credential2.workspaceId === requestedWorkspace;
|
|
35105
|
+
});
|
|
35106
|
+
if (existing) {
|
|
35107
|
+
applyCredential(env, existing);
|
|
35108
|
+
return { status: "configured", source: "local_store" };
|
|
35109
|
+
}
|
|
35110
|
+
const connectUrl = env.BRIEF_CONNECT_URL?.trim();
|
|
35111
|
+
const connectCode = env.BRIEF_CONNECT_CODE?.trim();
|
|
35112
|
+
if (!connectUrl || !connectCode) {
|
|
35113
|
+
return {
|
|
35114
|
+
status: "skipped",
|
|
35115
|
+
reason: "no_cloud_credential_or_pairing_setup"
|
|
35116
|
+
};
|
|
35117
|
+
}
|
|
35118
|
+
let response;
|
|
35119
|
+
try {
|
|
35120
|
+
response = await fetch(connectUrl, {
|
|
35121
|
+
method: "POST",
|
|
35122
|
+
headers: {
|
|
35123
|
+
accept: "application/json",
|
|
35124
|
+
"content-type": "application/json"
|
|
35125
|
+
},
|
|
35126
|
+
body: JSON.stringify({ code: connectCode }),
|
|
35127
|
+
signal: AbortSignal.timeout(1e4)
|
|
35128
|
+
});
|
|
35129
|
+
} catch {
|
|
35130
|
+
return {
|
|
35131
|
+
status: "failed",
|
|
35132
|
+
reason: "Could not reach the Brief connection service. Check your network and try a fresh setup from the Brief app."
|
|
35133
|
+
};
|
|
35134
|
+
}
|
|
35135
|
+
const body = await response.json().catch(() => null);
|
|
35136
|
+
if (!response.ok || !body?.ok || !body.apiUrl || !body.apiKey || !apiKeyPattern.test(body.apiKey) || !body.workspaceId || !body.privacyMode) {
|
|
35137
|
+
return {
|
|
35138
|
+
status: "failed",
|
|
35139
|
+
reason: body?.message ?? "This Brief setup link is expired or already used. Create a fresh connection from the Brief app."
|
|
35140
|
+
};
|
|
35141
|
+
}
|
|
35142
|
+
const credential = {
|
|
35143
|
+
apiUrl: body.apiUrl,
|
|
35144
|
+
apiKey: body.apiKey,
|
|
35145
|
+
workspaceId: body.workspaceId,
|
|
35146
|
+
privacyMode: body.privacyMode,
|
|
35147
|
+
...body.connectorId ? { connectorId: body.connectorId } : {},
|
|
35148
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35149
|
+
};
|
|
35150
|
+
try {
|
|
35151
|
+
writeCredentialFile(credentialPath, [
|
|
35152
|
+
...stored.credentials.filter(
|
|
35153
|
+
(item) => item.workspaceId !== credential.workspaceId || originFrom(item.apiUrl) !== originFrom(credential.apiUrl)
|
|
35154
|
+
),
|
|
35155
|
+
credential
|
|
35156
|
+
]);
|
|
35157
|
+
} catch {
|
|
35158
|
+
return {
|
|
35159
|
+
status: "failed",
|
|
35160
|
+
reason: "Brief connected, but could not save the local credential. Check permissions for the local Brief config directory and try again."
|
|
35161
|
+
};
|
|
35162
|
+
}
|
|
35163
|
+
applyCredential(env, credential);
|
|
35164
|
+
return { status: "configured", source: "pairing" };
|
|
35165
|
+
}
|
|
35166
|
+
function forgetCloudCredentials(env = process.env) {
|
|
35167
|
+
const credentialPath = credentialsPath(env);
|
|
35168
|
+
const stored = readCredentialFile(credentialPath);
|
|
35169
|
+
const requestedOrigin = originFrom(
|
|
35170
|
+
env.BRIEF_API_URL ?? env.BRIEF_CONNECT_URL
|
|
35171
|
+
);
|
|
35172
|
+
const requestedWorkspace = env.BRIEF_WORKSPACE_ID?.trim();
|
|
35173
|
+
const remaining = stored.credentials.filter((credential) => {
|
|
35174
|
+
if (requestedOrigin && originFrom(credential.apiUrl) !== requestedOrigin) {
|
|
35175
|
+
return true;
|
|
35176
|
+
}
|
|
35177
|
+
return Boolean(
|
|
35178
|
+
requestedWorkspace && credential.workspaceId !== requestedWorkspace
|
|
35179
|
+
);
|
|
35180
|
+
});
|
|
35181
|
+
writeCredentialFile(credentialPath, remaining);
|
|
35182
|
+
}
|
|
35183
|
+
function defaultCredentialsPath(env = process.env) {
|
|
35184
|
+
return credentialsPath(env);
|
|
35185
|
+
}
|
|
35186
|
+
function credentialsPath(env) {
|
|
35187
|
+
return env.BRIEF_CREDENTIALS_FILE?.trim() || path14.join(
|
|
35188
|
+
env.XDG_CONFIG_HOME?.trim() || path14.join(os.homedir(), ".config"),
|
|
35189
|
+
"runbrief",
|
|
35190
|
+
"credentials.json"
|
|
35191
|
+
);
|
|
35192
|
+
}
|
|
35193
|
+
function readCredentialFile(filePath) {
|
|
35194
|
+
try {
|
|
35195
|
+
const parsed = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
35196
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.credentials)) {
|
|
35197
|
+
return { version: 1, credentials: [] };
|
|
35198
|
+
}
|
|
35199
|
+
return {
|
|
35200
|
+
version: 1,
|
|
35201
|
+
credentials: parsed.credentials.filter(isStoredCredential)
|
|
35202
|
+
};
|
|
35203
|
+
} catch {
|
|
35204
|
+
return { version: 1, credentials: [] };
|
|
35205
|
+
}
|
|
35206
|
+
}
|
|
35207
|
+
function writeCredentialFile(filePath, credentials) {
|
|
35208
|
+
mkdirSync2(path14.dirname(filePath), { recursive: true, mode: 448 });
|
|
35209
|
+
chmodSync(path14.dirname(filePath), 448);
|
|
35210
|
+
writeFileSync5(
|
|
35211
|
+
filePath,
|
|
35212
|
+
`${JSON.stringify({ version: 1, credentials }, null, 2)}
|
|
35213
|
+
`,
|
|
35214
|
+
{ encoding: "utf8", mode: 384 }
|
|
35215
|
+
);
|
|
35216
|
+
chmodSync(filePath, 384);
|
|
35217
|
+
}
|
|
35218
|
+
function applyCredential(env, credential) {
|
|
35219
|
+
env.BRIEF_API_URL = credential.apiUrl;
|
|
35220
|
+
env.BRIEF_API_KEY = credential.apiKey;
|
|
35221
|
+
env.BRIEF_WORKSPACE_ID = credential.workspaceId;
|
|
35222
|
+
env.BRIEF_PRIVACY_MODE = credential.privacyMode;
|
|
35223
|
+
}
|
|
35224
|
+
function originFrom(value) {
|
|
35225
|
+
if (!value) return void 0;
|
|
35226
|
+
try {
|
|
35227
|
+
return new URL(value).origin;
|
|
35228
|
+
} catch {
|
|
35229
|
+
return void 0;
|
|
35230
|
+
}
|
|
35231
|
+
}
|
|
35232
|
+
function isStoredCredential(value) {
|
|
35233
|
+
if (!value || typeof value !== "object") return false;
|
|
35234
|
+
const candidate = value;
|
|
35235
|
+
return Boolean(
|
|
35236
|
+
typeof candidate.apiUrl === "string" && typeof candidate.apiKey === "string" && apiKeyPattern.test(candidate.apiKey) && typeof candidate.workspaceId === "string" && typeof candidate.privacyMode === "string" && typeof candidate.updatedAt === "string"
|
|
35237
|
+
);
|
|
35238
|
+
}
|
|
35239
|
+
var apiKeyPattern;
|
|
35240
|
+
var init_cloudCredentials = __esm({
|
|
35241
|
+
"../mcp/src/cloudCredentials.ts"() {
|
|
35242
|
+
"use strict";
|
|
35243
|
+
apiKeyPattern = /^brf_(live|test)_[a-zA-Z0-9]{8,}_[a-zA-Z0-9]{24,}$/;
|
|
35244
|
+
}
|
|
35245
|
+
});
|
|
35246
|
+
|
|
35247
|
+
// ../mcp/src/workspace.ts
|
|
35248
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
35249
|
+
import path15 from "node:path";
|
|
34135
35250
|
function resolveRepoRoot(repoPath, options2 = {}) {
|
|
34136
35251
|
return resolveRepoRootDetails(repoPath, options2).repoRoot;
|
|
34137
35252
|
}
|
|
34138
35253
|
function resolveRepoRootDetails(repoPath, options2 = {}) {
|
|
34139
|
-
const cwd =
|
|
35254
|
+
const cwd = path15.resolve(options2.cwd ?? process.cwd());
|
|
34140
35255
|
const env = options2.env ?? process.env;
|
|
34141
35256
|
const cwdResolution = resolveExistingRoot(cwd, "cwd");
|
|
34142
35257
|
const cwdIsCustomerWorktree = isGitWorktree(cwdResolution.repoRoot) && !looksLikeBriefSourceCheckout(cwdResolution.repoRoot);
|
|
@@ -34150,7 +35265,7 @@ function resolveRepoRootDetails(repoPath, options2 = {}) {
|
|
|
34150
35265
|
]);
|
|
34151
35266
|
const explicitRepoPath = repoPath?.trim();
|
|
34152
35267
|
if (explicitRepoPath) {
|
|
34153
|
-
const explicitPathKind =
|
|
35268
|
+
const explicitPathKind = path15.isAbsolute(explicitRepoPath) ? "absolute" : "relative";
|
|
34154
35269
|
if (explicitPathKind === "relative" && envCandidates.length > 1) {
|
|
34155
35270
|
return {
|
|
34156
35271
|
...cwdResolution,
|
|
@@ -34162,7 +35277,7 @@ function resolveRepoRootDetails(repoPath, options2 = {}) {
|
|
|
34162
35277
|
const relativeBase = cwdIsCustomerWorktree ? cwdResolution.repoRoot : envCandidates[0]?.repoRoot ?? cwd;
|
|
34163
35278
|
return {
|
|
34164
35279
|
...resolveExistingRoot(
|
|
34165
|
-
|
|
35280
|
+
path15.resolve(
|
|
34166
35281
|
explicitPathKind === "absolute" ? cwd : relativeBase,
|
|
34167
35282
|
explicitRepoPath
|
|
34168
35283
|
),
|
|
@@ -34200,7 +35315,7 @@ function resolveFromEnvVars(cwd, env, envVars, options2 = {}) {
|
|
|
34200
35315
|
for (const envVar of envVars) {
|
|
34201
35316
|
const value = cleanWorkspacePath(env[envVar]);
|
|
34202
35317
|
if (!value) continue;
|
|
34203
|
-
const start =
|
|
35318
|
+
const start = path15.resolve(cwd, value);
|
|
34204
35319
|
if (existsSync5(start)) {
|
|
34205
35320
|
const resolution = resolveExistingRoot(start, "environment", envVar);
|
|
34206
35321
|
if (options2.skipBriefSource && looksLikeBriefSourceCheckout(resolution.repoRoot)) {
|
|
@@ -34213,7 +35328,7 @@ function resolveFromEnvVars(cwd, env, envVars, options2 = {}) {
|
|
|
34213
35328
|
}
|
|
34214
35329
|
function repoTargetHealth(resolution, options2 = {}) {
|
|
34215
35330
|
const env = options2.env ?? process.env;
|
|
34216
|
-
const cwd =
|
|
35331
|
+
const cwd = path15.resolve(options2.cwd ?? process.cwd());
|
|
34217
35332
|
const gitRoot = runGit(resolution.repoRoot, ["rev-parse", "--show-toplevel"]);
|
|
34218
35333
|
const evidence = [
|
|
34219
35334
|
...rootEvidence(resolution, cwd, env),
|
|
@@ -34274,7 +35389,7 @@ function repoTargetHealth(resolution, options2 = {}) {
|
|
|
34274
35389
|
};
|
|
34275
35390
|
}
|
|
34276
35391
|
function resolveExistingRoot(start, source, envVar) {
|
|
34277
|
-
const gitStart = existsSync5(start) && !statSync4(start).isDirectory() ?
|
|
35392
|
+
const gitStart = existsSync5(start) && !statSync4(start).isDirectory() ? path15.dirname(start) : start;
|
|
34278
35393
|
const gitRoot = runGit(gitStart, ["rev-parse", "--show-toplevel"]);
|
|
34279
35394
|
return {
|
|
34280
35395
|
repoRoot: gitRoot && existsSync5(gitRoot) ? gitRoot : start,
|
|
@@ -34325,12 +35440,12 @@ function preferPinnedRepoRoot(env) {
|
|
|
34325
35440
|
return env.BRIEF_REPO_ROOT_MODE === "pinned" || env.BRIEF_REPO_ROOT_PINNED === "true";
|
|
34326
35441
|
}
|
|
34327
35442
|
function looksLikeBriefSourceCheckout(repoRoot) {
|
|
34328
|
-
if (!existsSync5(
|
|
35443
|
+
if (!existsSync5(path15.join(repoRoot, "packages", "mcp", "src", "server.ts"))) {
|
|
34329
35444
|
return false;
|
|
34330
35445
|
}
|
|
34331
35446
|
try {
|
|
34332
35447
|
const pkg = JSON.parse(
|
|
34333
|
-
|
|
35448
|
+
readFileSync4(path15.join(repoRoot, "package.json"), "utf8")
|
|
34334
35449
|
);
|
|
34335
35450
|
return pkg.name === "brief";
|
|
34336
35451
|
} catch {
|
|
@@ -34359,7 +35474,7 @@ var init_workspace = __esm({
|
|
|
34359
35474
|
});
|
|
34360
35475
|
|
|
34361
35476
|
// ../mcp/src/connectorReadiness.ts
|
|
34362
|
-
import
|
|
35477
|
+
import path16 from "node:path";
|
|
34363
35478
|
function connectorReadiness(input) {
|
|
34364
35479
|
const runtimeLabel = input.mode === "cli" ? "Brief CLI process" : "Brief MCP connector";
|
|
34365
35480
|
const installStatusTool = input.mode === "cli" ? "install-status or brief.install_status" : "brief.install_status";
|
|
@@ -34453,8 +35568,8 @@ function connectorReadiness(input) {
|
|
|
34453
35568
|
};
|
|
34454
35569
|
}
|
|
34455
35570
|
function briefSourceCheckoutMismatch(input) {
|
|
34456
|
-
const repoRoot = input.repoRoot ?
|
|
34457
|
-
const sourceRoot = input.freshness.sourceRoot ?
|
|
35571
|
+
const repoRoot = input.repoRoot ? path16.resolve(input.repoRoot) : void 0;
|
|
35572
|
+
const sourceRoot = input.freshness.sourceRoot ? path16.resolve(input.freshness.sourceRoot) : void 0;
|
|
34458
35573
|
if (!repoRoot || !sourceRoot) return void 0;
|
|
34459
35574
|
if (samePath(repoRoot, sourceRoot)) return void 0;
|
|
34460
35575
|
if (!looksLikeBriefSourceCheckout(repoRoot)) return void 0;
|
|
@@ -34469,7 +35584,7 @@ function briefSourceCheckoutMismatch(input) {
|
|
|
34469
35584
|
};
|
|
34470
35585
|
}
|
|
34471
35586
|
function samePath(left, right) {
|
|
34472
|
-
return
|
|
35587
|
+
return path16.normalize(left) === path16.normalize(right);
|
|
34473
35588
|
}
|
|
34474
35589
|
var init_connectorReadiness = __esm({
|
|
34475
35590
|
"../mcp/src/connectorReadiness.ts"() {
|
|
@@ -34489,9 +35604,9 @@ function buildConnectorRecoveryPlan(input) {
|
|
|
34489
35604
|
});
|
|
34490
35605
|
const restartNeeded = readiness.status === "restart_required" || readiness.status === "source_mismatch";
|
|
34491
35606
|
const needsVerification = readiness.status === "needs_verification";
|
|
34492
|
-
const canSelfRestart = input.mode === "server" && readiness.status !== "
|
|
35607
|
+
const canSelfRestart = input.mode === "server" && readiness.status !== "wrong_repo";
|
|
34493
35608
|
const confirmed = input.confirm === briefRestartConfirmation;
|
|
34494
|
-
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);
|
|
34495
35610
|
const status = shouldSchedule ? "restart_scheduled" : restartNeeded ? readiness.status : needsVerification ? "needs_verification" : "ready";
|
|
34496
35611
|
const verifyExpect = [
|
|
34497
35612
|
"connectorReadiness.status is ready",
|
|
@@ -34515,7 +35630,7 @@ function buildConnectorRecoveryPlan(input) {
|
|
|
34515
35630
|
mcpSelfRestart: {
|
|
34516
35631
|
tool: "brief.restart_connector",
|
|
34517
35632
|
available: canSelfRestart,
|
|
34518
|
-
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.",
|
|
34519
35634
|
call: {
|
|
34520
35635
|
confirm: briefRestartConfirmation,
|
|
34521
35636
|
surface: selectedSurface,
|
|
@@ -34538,7 +35653,8 @@ function buildConnectorRecoveryPlan(input) {
|
|
|
34538
35653
|
}
|
|
34539
35654
|
function summaryForStatus(status, readiness) {
|
|
34540
35655
|
if (status === "restart_scheduled") {
|
|
34541
|
-
|
|
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;
|
|
34542
35658
|
}
|
|
34543
35659
|
return readiness.summary;
|
|
34544
35660
|
}
|
|
@@ -34603,8 +35719,8 @@ var init_connectorRecovery = __esm({
|
|
|
34603
35719
|
});
|
|
34604
35720
|
|
|
34605
35721
|
// ../mcp/src/serverFreshness.ts
|
|
34606
|
-
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as
|
|
34607
|
-
import
|
|
35722
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "node:fs";
|
|
35723
|
+
import path17 from "node:path";
|
|
34608
35724
|
function mcpServerFreshness(options2) {
|
|
34609
35725
|
const cwd = options2.cwd ?? process.cwd();
|
|
34610
35726
|
const now = options2.now ?? /* @__PURE__ */ new Date();
|
|
@@ -34615,10 +35731,12 @@ function mcpServerFreshness(options2) {
|
|
|
34615
35731
|
cwd,
|
|
34616
35732
|
sourceRoot: options2.sourceRoot
|
|
34617
35733
|
});
|
|
35734
|
+
const sourceKind = options2.distribution ?? connectorDistributionFor(options2.entrypoint);
|
|
34618
35735
|
const watchedGlobs = watchedSourceGlobs();
|
|
34619
35736
|
const base = {
|
|
34620
35737
|
serverStartedAt: options2.serverStartedAt.toISOString(),
|
|
34621
35738
|
checkedAt: now.toISOString(),
|
|
35739
|
+
sourceKind,
|
|
34622
35740
|
process: {
|
|
34623
35741
|
pid: process.pid,
|
|
34624
35742
|
cwd,
|
|
@@ -34633,6 +35751,15 @@ function mcpServerFreshness(options2) {
|
|
|
34633
35751
|
changedSinceStartCount: 0
|
|
34634
35752
|
};
|
|
34635
35753
|
if (!sourceRoot) {
|
|
35754
|
+
if (sourceKind === "package") {
|
|
35755
|
+
return {
|
|
35756
|
+
status: "current",
|
|
35757
|
+
...base,
|
|
35758
|
+
action: freshnessAction("current"),
|
|
35759
|
+
summary: "The published Brief connector is running from an immutable package; source-checkout freshness checks are not applicable.",
|
|
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."
|
|
35761
|
+
};
|
|
35762
|
+
}
|
|
34636
35763
|
return {
|
|
34637
35764
|
status: "unknown",
|
|
34638
35765
|
...base,
|
|
@@ -34673,6 +35800,21 @@ function mcpServerFreshness(options2) {
|
|
|
34673
35800
|
next: status === "restart_recommended" ? "Call brief.restart_connector from the coding agent, then call brief.install_status again and confirm serverFreshness.status is current." : "Continue using Brief normally."
|
|
34674
35801
|
};
|
|
34675
35802
|
}
|
|
35803
|
+
function connectorDistributionFor(entrypoint) {
|
|
35804
|
+
const configured = process.env.BRIEF_CONNECTOR_DISTRIBUTION;
|
|
35805
|
+
if (configured === "package" || configured === "source_checkout") {
|
|
35806
|
+
return configured;
|
|
35807
|
+
}
|
|
35808
|
+
const normalizedEntrypoint = path17.normalize(entrypoint);
|
|
35809
|
+
if (normalizedEntrypoint.includes(
|
|
35810
|
+
`${path17.sep}node_modules${path17.sep}runbrief${path17.sep}`
|
|
35811
|
+
) || normalizedEntrypoint.includes(
|
|
35812
|
+
`${path17.sep}packages${path17.sep}runbrief${path17.sep}`
|
|
35813
|
+
)) {
|
|
35814
|
+
return "package";
|
|
35815
|
+
}
|
|
35816
|
+
return "unknown";
|
|
35817
|
+
}
|
|
34676
35818
|
function freshnessAction(status) {
|
|
34677
35819
|
if (status === "current") {
|
|
34678
35820
|
return {
|
|
@@ -34707,30 +35849,30 @@ function findBriefSourceRoot(options2) {
|
|
|
34707
35849
|
const candidates = [
|
|
34708
35850
|
options2.sourceRoot,
|
|
34709
35851
|
process.env.BRIEF_SOURCE_ROOT,
|
|
34710
|
-
options2.entrypoint ?
|
|
35852
|
+
options2.entrypoint ? path17.dirname(options2.entrypoint) : void 0,
|
|
34711
35853
|
options2.cwd
|
|
34712
35854
|
].filter((candidate) => Boolean(candidate));
|
|
34713
35855
|
for (const candidate of candidates) {
|
|
34714
|
-
const root = walkUpForBriefRoot(
|
|
35856
|
+
const root = walkUpForBriefRoot(path17.resolve(candidate));
|
|
34715
35857
|
if (root) return root;
|
|
34716
35858
|
}
|
|
34717
35859
|
return void 0;
|
|
34718
35860
|
}
|
|
34719
35861
|
function walkUpForBriefRoot(start) {
|
|
34720
|
-
let current = existsSync6(start) && statSafe(start)?.isFile() ?
|
|
35862
|
+
let current = existsSync6(start) && statSafe(start)?.isFile() ? path17.dirname(start) : start;
|
|
34721
35863
|
while (true) {
|
|
34722
35864
|
if (isBriefSourceRoot(current)) return current;
|
|
34723
|
-
const parent =
|
|
35865
|
+
const parent = path17.dirname(current);
|
|
34724
35866
|
if (parent === current) return void 0;
|
|
34725
35867
|
current = parent;
|
|
34726
35868
|
}
|
|
34727
35869
|
}
|
|
34728
35870
|
function isBriefSourceRoot(candidate) {
|
|
34729
|
-
const packageJsonPath =
|
|
35871
|
+
const packageJsonPath = path17.join(candidate, "package.json");
|
|
34730
35872
|
if (!existsSync6(packageJsonPath)) return false;
|
|
34731
35873
|
const packageJson = readJson(packageJsonPath);
|
|
34732
35874
|
if (packageJson?.name !== "brief") return false;
|
|
34733
|
-
return existsSync6(
|
|
35875
|
+
return existsSync6(path17.join(candidate, "packages", "mcp")) && existsSync6(path17.join(candidate, "packages", "core")) && existsSync6(path17.join(candidate, "packages", "contracts"));
|
|
34734
35876
|
}
|
|
34735
35877
|
function watchedSourceGlobs() {
|
|
34736
35878
|
return [
|
|
@@ -34768,7 +35910,7 @@ function collectWatchedFiles(sourceRoot, maxWatchedFiles) {
|
|
|
34768
35910
|
truncated = true;
|
|
34769
35911
|
break;
|
|
34770
35912
|
}
|
|
34771
|
-
const absolutePath =
|
|
35913
|
+
const absolutePath = path17.join(sourceRoot, relativePath);
|
|
34772
35914
|
const stats = statSafe(absolutePath);
|
|
34773
35915
|
if (!stats) continue;
|
|
34774
35916
|
if (stats.isDirectory()) {
|
|
@@ -34785,7 +35927,7 @@ function collectWatchedFiles(sourceRoot, maxWatchedFiles) {
|
|
|
34785
35927
|
break;
|
|
34786
35928
|
}
|
|
34787
35929
|
files.push({
|
|
34788
|
-
path: normalizePath(
|
|
35930
|
+
path: normalizePath(path17.relative(sourceRoot, absolutePath)),
|
|
34789
35931
|
modifiedAtMs: stats.mtimeMs
|
|
34790
35932
|
});
|
|
34791
35933
|
}
|
|
@@ -34796,7 +35938,7 @@ function walkFiles2(sourceRoot, directory, files, maxWatchedFiles) {
|
|
|
34796
35938
|
const entries = readdirSafe(directory);
|
|
34797
35939
|
for (const entry of entries) {
|
|
34798
35940
|
if (files.length >= maxWatchedFiles) return { truncated: true };
|
|
34799
|
-
const absolutePath =
|
|
35941
|
+
const absolutePath = path17.join(directory, entry.name);
|
|
34800
35942
|
if (entry.isDirectory()) {
|
|
34801
35943
|
if (shouldSkipDirectory(entry.name)) continue;
|
|
34802
35944
|
const result = walkFiles2(
|
|
@@ -34812,7 +35954,7 @@ function walkFiles2(sourceRoot, directory, files, maxWatchedFiles) {
|
|
|
34812
35954
|
const stats = statSafe(absolutePath);
|
|
34813
35955
|
if (!stats) continue;
|
|
34814
35956
|
files.push({
|
|
34815
|
-
path: normalizePath(
|
|
35957
|
+
path: normalizePath(path17.relative(sourceRoot, absolutePath)),
|
|
34816
35958
|
modifiedAtMs: stats.mtimeMs
|
|
34817
35959
|
});
|
|
34818
35960
|
}
|
|
@@ -34831,13 +35973,13 @@ function shouldSkipDirectory(name) {
|
|
|
34831
35973
|
);
|
|
34832
35974
|
}
|
|
34833
35975
|
function shouldWatchFile(filePath) {
|
|
34834
|
-
const base =
|
|
35976
|
+
const base = path17.basename(filePath);
|
|
34835
35977
|
if (base.endsWith(".tsbuildinfo")) return false;
|
|
34836
35978
|
if (base === ".DS_Store") return false;
|
|
34837
35979
|
return true;
|
|
34838
35980
|
}
|
|
34839
35981
|
function normalizePath(value) {
|
|
34840
|
-
return value.split(
|
|
35982
|
+
return value.split(path17.sep).join("/");
|
|
34841
35983
|
}
|
|
34842
35984
|
function statSafe(filePath) {
|
|
34843
35985
|
try {
|
|
@@ -34855,7 +35997,7 @@ function readdirSafe(directory) {
|
|
|
34855
35997
|
}
|
|
34856
35998
|
function readJson(filePath) {
|
|
34857
35999
|
try {
|
|
34858
|
-
return JSON.parse(
|
|
36000
|
+
return JSON.parse(readFileSync5(filePath, "utf8"));
|
|
34859
36001
|
} catch {
|
|
34860
36002
|
return void 0;
|
|
34861
36003
|
}
|
|
@@ -34918,13 +36060,16 @@ var init_serverInstructions = __esm({
|
|
|
34918
36060
|
"../mcp/src/serverInstructions.ts"() {
|
|
34919
36061
|
"use strict";
|
|
34920
36062
|
briefMcpInstructions = [
|
|
34921
|
-
"Brief is the default repo-context, reuse, review, and handoff companion for non-trivial coding work.
|
|
34922
|
-
"At the first
|
|
34923
|
-
"
|
|
34924
|
-
"
|
|
34925
|
-
"
|
|
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.",
|
|
34926
36071
|
"Before a meaningful handoff, call brief.review_diff. Save and verify a handoff only for multi-step, high-risk, or durable work.",
|
|
34927
|
-
"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."
|
|
34928
36073
|
].join("\n");
|
|
34929
36074
|
briefMcpInstructionSignals = [
|
|
34930
36075
|
"brief.install_status",
|
|
@@ -34968,6 +36113,18 @@ function compactInstallStatus(input) {
|
|
|
34968
36113
|
const degraded = !blocked && (Boolean(cloudIssue) || input.readiness.verdict !== "agent_ready" || input.workflowWiring.status === "missing" || input.workflowWiring.status === "manual_workflow_only");
|
|
34969
36114
|
const stats = input.scan.stats;
|
|
34970
36115
|
const mcpContractActive = input.serverInstructionsActive && input.connectorReadiness.safeToUseBrief;
|
|
36116
|
+
const teamSyncStatus = input.cloudSync.configured ? input.teamContext.status === "failed" ? "unavailable" : input.teamContext.status === "fetched" ? "connected" : "checking" : "not_connected";
|
|
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
|
+
};
|
|
34971
36128
|
const totalMapCandidates = stats.mapTotalCandidateCount ?? stats.mapNodeCount + (stats.mapOmittedCount ?? 0);
|
|
34972
36129
|
const mapCoveragePercent = totalMapCandidates ? Math.round(stats.mapNodeCount / totalMapCandidates * 100) : 100;
|
|
34973
36130
|
const repoGuidanceSignals = unique4(
|
|
@@ -34996,9 +36153,17 @@ function compactInstallStatus(input) {
|
|
|
34996
36153
|
status: input.connectorReadiness.status,
|
|
34997
36154
|
safeToUse: input.connectorReadiness.safeToUseBrief,
|
|
34998
36155
|
fresh: input.serverFreshness.status === "current",
|
|
36156
|
+
distribution: input.serverFreshness.sourceKind,
|
|
34999
36157
|
sourceRoot: input.serverFreshness.sourceRoot,
|
|
36158
|
+
update: connectorUpdate,
|
|
35000
36159
|
summary: input.connectorReadiness.summary
|
|
35001
36160
|
},
|
|
36161
|
+
connection: {
|
|
36162
|
+
mode: connectionMode,
|
|
36163
|
+
localAvailable: input.connectorReadiness.safeToUseBrief,
|
|
36164
|
+
teamSync: teamSyncStatus,
|
|
36165
|
+
summary: connectionMode === "team_degraded" ? "Local repo work is available. Team sync needs attention and will not block local analysis." : connectionMode === "team" ? "Local repo work and approved team context are available." : "Local repo work is available without an account. Team sync is optional."
|
|
36166
|
+
},
|
|
35002
36167
|
cloud: {
|
|
35003
36168
|
configured: input.cloudSync.configured,
|
|
35004
36169
|
privacyMode: input.cloudSync.privacyMode,
|
|
@@ -35054,8 +36219,9 @@ var briefConnectorVersion;
|
|
|
35054
36219
|
var init_installStatus = __esm({
|
|
35055
36220
|
"../mcp/src/installStatus.ts"() {
|
|
35056
36221
|
"use strict";
|
|
36222
|
+
init_dist2();
|
|
35057
36223
|
init_serverInstructions();
|
|
35058
|
-
briefConnectorVersion = "0.1.
|
|
36224
|
+
briefConnectorVersion = "0.1.4";
|
|
35059
36225
|
}
|
|
35060
36226
|
});
|
|
35061
36227
|
|
|
@@ -35075,8 +36241,7 @@ function buildCompactStartEnvelope(input) {
|
|
|
35075
36241
|
route: {
|
|
35076
36242
|
tool: start.intent.tool,
|
|
35077
36243
|
...start.intent.recipe ? { recipe: start.intent.recipe } : {},
|
|
35078
|
-
...start.intent.autonomy ? { autonomy: start.intent.autonomy } : {}
|
|
35079
|
-
...start.intent.reviewBar ? { reviewBar: start.intent.reviewBar } : {}
|
|
36244
|
+
...start.intent.autonomy ? { autonomy: start.intent.autonomy } : {}
|
|
35080
36245
|
},
|
|
35081
36246
|
workPacket: (() => {
|
|
35082
36247
|
const packet = buildAgentWorkPacket({
|
|
@@ -35086,7 +36251,6 @@ function buildCompactStartEnvelope(input) {
|
|
|
35086
36251
|
nextAction: input.next
|
|
35087
36252
|
});
|
|
35088
36253
|
return {
|
|
35089
|
-
schemaVersion: packet.schemaVersion,
|
|
35090
36254
|
compactText: packet.compactText
|
|
35091
36255
|
};
|
|
35092
36256
|
})(),
|
|
@@ -35241,15 +36405,15 @@ var init_startSummary = __esm({
|
|
|
35241
36405
|
});
|
|
35242
36406
|
|
|
35243
36407
|
// ../mcp/src/wikiInit.ts
|
|
35244
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
35245
|
-
import
|
|
36408
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
36409
|
+
import path18 from "node:path";
|
|
35246
36410
|
function writeWikiInitArtifacts(repoRoot, wiki, options2) {
|
|
35247
|
-
const wikiDir =
|
|
35248
|
-
const pagesDir =
|
|
35249
|
-
|
|
36411
|
+
const wikiDir = path18.join(repoRoot, ".brief", "wiki");
|
|
36412
|
+
const pagesDir = path18.join(wikiDir, "pages");
|
|
36413
|
+
mkdirSync3(pagesDir, { recursive: true });
|
|
35250
36414
|
const pageArtifacts = wiki.pages.map((page2) => {
|
|
35251
|
-
const pagePath =
|
|
35252
|
-
|
|
36415
|
+
const pagePath = path18.join(pagesDir, `${wikiPageSlug(page2)}.md`);
|
|
36416
|
+
writeFileSync6(pagePath, renderWikiPageMarkdown(wiki, page2));
|
|
35253
36417
|
return {
|
|
35254
36418
|
id: page2.id,
|
|
35255
36419
|
title: page2.title,
|
|
@@ -35258,32 +36422,32 @@ function writeWikiInitArtifacts(repoRoot, wiki, options2) {
|
|
|
35258
36422
|
quality: page2.quality.status
|
|
35259
36423
|
};
|
|
35260
36424
|
});
|
|
35261
|
-
const readmePath =
|
|
35262
|
-
|
|
35263
|
-
const manifestPath =
|
|
35264
|
-
|
|
36425
|
+
const readmePath = path18.join(wikiDir, "README.md");
|
|
36426
|
+
writeFileSync6(readmePath, renderWikiReadme(wiki, pageArtifacts));
|
|
36427
|
+
const manifestPath = path18.join(wikiDir, "manifest.json");
|
|
36428
|
+
writeFileSync6(
|
|
35265
36429
|
manifestPath,
|
|
35266
36430
|
`${JSON.stringify(wikiInitManifest(wiki, pageArtifacts), null, 2)}
|
|
35267
36431
|
`
|
|
35268
36432
|
);
|
|
35269
|
-
const workflowTemplatePath =
|
|
36433
|
+
const workflowTemplatePath = path18.join(
|
|
35270
36434
|
wikiDir,
|
|
35271
36435
|
"brief-wiki-refresh.github-actions.yml"
|
|
35272
36436
|
);
|
|
35273
|
-
|
|
36437
|
+
writeFileSync6(workflowTemplatePath, renderWikiRefreshWorkflow());
|
|
35274
36438
|
const agentFiles = options2.writeAgentFiles ? ["AGENTS.md", "CLAUDE.md"].map(
|
|
35275
36439
|
(fileName) => upsertWikiAgentInstructions(repoRoot, fileName)
|
|
35276
36440
|
) : [];
|
|
35277
36441
|
let githubWorkflow2;
|
|
35278
36442
|
if (options2.writeGithubWorkflow) {
|
|
35279
|
-
const workflowPath =
|
|
36443
|
+
const workflowPath = path18.join(
|
|
35280
36444
|
repoRoot,
|
|
35281
36445
|
".github",
|
|
35282
36446
|
"workflows",
|
|
35283
36447
|
"brief-wiki-refresh.yml"
|
|
35284
36448
|
);
|
|
35285
|
-
|
|
35286
|
-
|
|
36449
|
+
mkdirSync3(path18.dirname(workflowPath), { recursive: true });
|
|
36450
|
+
writeFileSync6(workflowPath, renderWikiRefreshWorkflow({ active: true }));
|
|
35287
36451
|
githubWorkflow2 = {
|
|
35288
36452
|
path: repoRelativePath(repoRoot, workflowPath),
|
|
35289
36453
|
status: "written",
|
|
@@ -35473,12 +36637,12 @@ function wikiInitManifest(wiki, pageArtifacts) {
|
|
|
35473
36637
|
};
|
|
35474
36638
|
}
|
|
35475
36639
|
function upsertWikiAgentInstructions(repoRoot, fileName) {
|
|
35476
|
-
const filePath =
|
|
36640
|
+
const filePath = path18.join(repoRoot, fileName);
|
|
35477
36641
|
const status = existsSync7(filePath) ? "updated" : "created";
|
|
35478
|
-
const existing = status === "updated" ?
|
|
36642
|
+
const existing = status === "updated" ? readFileSync6(filePath, "utf8") : "";
|
|
35479
36643
|
const initial = existing || `# ${fileName}
|
|
35480
36644
|
`;
|
|
35481
|
-
|
|
36645
|
+
writeFileSync6(filePath, upsertMarkedBlock(initial, wikiAgentBlock()));
|
|
35482
36646
|
return {
|
|
35483
36647
|
path: repoRelativePath(repoRoot, filePath),
|
|
35484
36648
|
status,
|
|
@@ -35550,7 +36714,7 @@ function renderWikiRefreshWorkflow(options2 = {}) {
|
|
|
35550
36714
|
" with:",
|
|
35551
36715
|
" node-version: 22",
|
|
35552
36716
|
" - name: Refresh wiki",
|
|
35553
|
-
|
|
36717
|
+
` run: npx -y ${briefPublishedPackageInstallSpec} wiki init --repo "$GITHUB_WORKSPACE" --update --no-agent-files`,
|
|
35554
36718
|
" - name: Open refresh PR",
|
|
35555
36719
|
" uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11",
|
|
35556
36720
|
" with:",
|
|
@@ -35571,7 +36735,7 @@ function markdownList(items) {
|
|
|
35571
36735
|
return items.map((item) => `- ${item}`).join("\n");
|
|
35572
36736
|
}
|
|
35573
36737
|
function repoRelativePath(repoRoot, filePath) {
|
|
35574
|
-
return
|
|
36738
|
+
return path18.relative(repoRoot, filePath).split(path18.sep).join("/");
|
|
35575
36739
|
}
|
|
35576
36740
|
function wikiReadmeRelativePath(repoRelativeFilePath) {
|
|
35577
36741
|
return repoRelativeFilePath.replace(/^\.brief\/wiki\//, "");
|
|
@@ -35666,8 +36830,8 @@ function bestMatchingReceipt(receipts, kind, input) {
|
|
|
35666
36830
|
function missionIntentMatches(receipt, input) {
|
|
35667
36831
|
if (input.recipeId && receipt.missionRecipeId === input.recipeId) return true;
|
|
35668
36832
|
if (!input.task) return false;
|
|
35669
|
-
const wantedTask =
|
|
35670
|
-
const receiptTask =
|
|
36833
|
+
const wantedTask = normalizeText2(input.task);
|
|
36834
|
+
const receiptTask = normalizeText2(receipt.task);
|
|
35671
36835
|
return receiptTask === wantedTask || wantedTask.length >= 20 && receiptTask.length >= 20 && (receiptTask.includes(wantedTask) || wantedTask.includes(receiptTask));
|
|
35672
36836
|
}
|
|
35673
36837
|
function matchScore(receipt, input) {
|
|
@@ -35677,8 +36841,8 @@ function matchScore(receipt, input) {
|
|
|
35677
36841
|
}
|
|
35678
36842
|
let score = input.branch && receiptBranch2 ? 15 : 0;
|
|
35679
36843
|
if (input.task) {
|
|
35680
|
-
const wantedTask =
|
|
35681
|
-
const receiptTask =
|
|
36844
|
+
const wantedTask = normalizeText2(input.task);
|
|
36845
|
+
const receiptTask = normalizeText2(receipt.task);
|
|
35682
36846
|
if (receiptTask === wantedTask) {
|
|
35683
36847
|
score += 80;
|
|
35684
36848
|
} else if (wantedTask.length >= 20 && receiptTask.length >= 20 && (receiptTask.includes(wantedTask) || wantedTask.includes(receiptTask))) {
|
|
@@ -35736,7 +36900,7 @@ function cleanValues(values, limit = 300) {
|
|
|
35736
36900
|
function unique5(values) {
|
|
35737
36901
|
return [...new Set(values)];
|
|
35738
36902
|
}
|
|
35739
|
-
function
|
|
36903
|
+
function normalizeText2(value) {
|
|
35740
36904
|
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
35741
36905
|
}
|
|
35742
36906
|
function normalizeBranch(value) {
|
|
@@ -35875,8 +37039,8 @@ var init_workflowWiringResponse = __esm({
|
|
|
35875
37039
|
|
|
35876
37040
|
// ../mcp/src/server.ts
|
|
35877
37041
|
var server_exports = {};
|
|
35878
|
-
import { chmodSync, mkdirSync as
|
|
35879
|
-
import
|
|
37042
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
37043
|
+
import path19 from "node:path";
|
|
35880
37044
|
import { fileURLToPath } from "node:url";
|
|
35881
37045
|
import {
|
|
35882
37046
|
McpServer
|
|
@@ -36137,7 +37301,8 @@ function serverFreshness() {
|
|
|
36137
37301
|
return mcpServerFreshness({
|
|
36138
37302
|
serverStartedAt,
|
|
36139
37303
|
entrypoint: serverModulePath,
|
|
36140
|
-
mode: "server"
|
|
37304
|
+
mode: "server",
|
|
37305
|
+
distribution: connectorDistributionFor(serverModulePath)
|
|
36141
37306
|
});
|
|
36142
37307
|
}
|
|
36143
37308
|
function summarizePacket(packet) {
|
|
@@ -36155,7 +37320,56 @@ function summarizePacket(packet) {
|
|
|
36155
37320
|
sources: packet.sources,
|
|
36156
37321
|
omitted: packet.omitted,
|
|
36157
37322
|
...packet.valueSignals ? { valueSignals: packet.valueSignals } : {},
|
|
36158
|
-
...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {}
|
|
37323
|
+
...packet.teamGuidance ? { teamGuidance: packet.teamGuidance } : {},
|
|
37324
|
+
...packet.dependencyRemediation ? { dependencyRemediation: packet.dependencyRemediation } : {}
|
|
37325
|
+
};
|
|
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."
|
|
36159
37373
|
};
|
|
36160
37374
|
}
|
|
36161
37375
|
function summarizeReview(review, detail = "auto") {
|
|
@@ -36593,6 +37807,7 @@ function summarizeBeforeYouCodeForStart(beforeYouCode) {
|
|
|
36593
37807
|
decision: beforeYouCode.reuseFirst.decision,
|
|
36594
37808
|
candidates: beforeYouCode.reuseFirst.candidates.slice(0, 2)
|
|
36595
37809
|
},
|
|
37810
|
+
mapCoverage: beforeYouCode.mapCoverage,
|
|
36596
37811
|
sharedContext: summarizeBeforeYouCodeSharedContext(
|
|
36597
37812
|
beforeYouCode.teamGuidance
|
|
36598
37813
|
),
|
|
@@ -36656,7 +37871,7 @@ function summarizeWorkflowWiringForStart(wiring) {
|
|
|
36656
37871
|
defaultAsk: wiring.defaultAsk,
|
|
36657
37872
|
gaps: wiring.gaps.slice(0, 3),
|
|
36658
37873
|
chatFeedback: wiring.chatFeedback.slice(0, 2),
|
|
36659
|
-
finalRecapPolicy: wiring.finalRecapPolicy,
|
|
37874
|
+
finalRecapPolicy: compactFinalRecapPolicy(wiring.finalRecapPolicy),
|
|
36660
37875
|
nextActions: wiring.nextActions.slice(0, 3)
|
|
36661
37876
|
};
|
|
36662
37877
|
}
|
|
@@ -36665,7 +37880,7 @@ function summarizeAutoEngageForStart(autoEngage) {
|
|
|
36665
37880
|
status: autoEngage.status,
|
|
36666
37881
|
summary: autoEngage.summary,
|
|
36667
37882
|
chatFeedback: autoEngage.chatFeedback.slice(0, 1),
|
|
36668
|
-
finalRecapPolicy: autoEngage.finalRecapPolicy,
|
|
37883
|
+
finalRecapPolicy: compactFinalRecapPolicy(autoEngage.finalRecapPolicy),
|
|
36669
37884
|
triggers: autoEngage.triggerMatrix.slice(0, 3).map((trigger) => ({
|
|
36670
37885
|
id: trigger.id,
|
|
36671
37886
|
label: trigger.label,
|
|
@@ -36674,6 +37889,13 @@ function summarizeAutoEngageForStart(autoEngage) {
|
|
|
36674
37889
|
nextActions: autoEngage.nextActions.slice(0, 2)
|
|
36675
37890
|
};
|
|
36676
37891
|
}
|
|
37892
|
+
function compactFinalRecapPolicy(policy) {
|
|
37893
|
+
return {
|
|
37894
|
+
maxLines: policy.maxLines,
|
|
37895
|
+
template: policy.defaultTemplate,
|
|
37896
|
+
fallbackWhenNotHelpful: policy.fallbackWhenNotHelpful
|
|
37897
|
+
};
|
|
37898
|
+
}
|
|
36677
37899
|
function summarizeWiki(wiki) {
|
|
36678
37900
|
return {
|
|
36679
37901
|
wikiId: wiki.wikiId,
|
|
@@ -36726,21 +37948,22 @@ function summarizeLocalReceipt(receipt) {
|
|
|
36726
37948
|
function writeHookFiles(repoRoot, files) {
|
|
36727
37949
|
const written = [];
|
|
36728
37950
|
for (const file of files) {
|
|
36729
|
-
const targetPath =
|
|
36730
|
-
|
|
36731
|
-
|
|
36732
|
-
if (file.path.endsWith(".sh"))
|
|
37951
|
+
const targetPath = path19.join(repoRoot, file.path);
|
|
37952
|
+
mkdirSync4(path19.dirname(targetPath), { recursive: true });
|
|
37953
|
+
writeFileSync7(targetPath, file.body);
|
|
37954
|
+
if (file.path.endsWith(".sh")) chmodSync2(targetPath, 493);
|
|
36733
37955
|
written.push(file.path);
|
|
36734
37956
|
}
|
|
36735
37957
|
return written;
|
|
36736
37958
|
}
|
|
36737
|
-
var server, personalizationKindSchema, referenceImplementationKindSchema, referenceImplementationStatusSchema, savePersonalizationToolSchema, workItemSchema, runtimeEvidenceSchema, handoffValidationSchema, artifactEvidenceSchema, loopCatalogFocusSchema, missionRecipeIdSchema2, startIntentSchema, nextStageSchema, responseDetailSchema, serverStartedAt, serverModulePath, repoTargetGuardExemptTools;
|
|
37959
|
+
var cloudBootstrap, server, personalizationKindSchema, referenceImplementationKindSchema, referenceImplementationStatusSchema, savePersonalizationToolSchema, workItemSchema, runtimeEvidenceSchema, handoffValidationSchema, artifactEvidenceSchema, loopCatalogFocusSchema, missionRecipeIdSchema2, startIntentSchema, nextStageSchema, responseDetailSchema, serverStartedAt, serverModulePath, repoTargetGuardExemptTools;
|
|
36738
37960
|
var init_server = __esm({
|
|
36739
|
-
"../mcp/src/server.ts"() {
|
|
37961
|
+
async "../mcp/src/server.ts"() {
|
|
36740
37962
|
"use strict";
|
|
36741
37963
|
init_dist();
|
|
36742
37964
|
init_dist2();
|
|
36743
37965
|
init_cloud();
|
|
37966
|
+
init_cloudCredentials();
|
|
36744
37967
|
init_teamContext();
|
|
36745
37968
|
init_missionInputOptions();
|
|
36746
37969
|
init_connectorReadiness();
|
|
@@ -36756,10 +37979,17 @@ var init_server = __esm({
|
|
|
36756
37979
|
init_workspace();
|
|
36757
37980
|
init_handoffAutofill();
|
|
36758
37981
|
init_scanFreshness();
|
|
37982
|
+
cloudBootstrap = process.env.BRIEF_CLOUD_BOOTSTRAP_DONE === "1" ? { status: "skipped", reason: "already_bootstrapped" } : await bootstrapCloudCredentials();
|
|
37983
|
+
process.env.BRIEF_CLOUD_BOOTSTRAP_DONE = "1";
|
|
37984
|
+
if (cloudBootstrap.status === "failed") {
|
|
37985
|
+
console.error(
|
|
37986
|
+
`Brief team sync is unavailable: ${cloudBootstrap.reason} Continuing in local mode.`
|
|
37987
|
+
);
|
|
37988
|
+
}
|
|
36759
37989
|
server = new McpServer(
|
|
36760
37990
|
{
|
|
36761
37991
|
name: "brief",
|
|
36762
|
-
version: "0.1.
|
|
37992
|
+
version: "0.1.4"
|
|
36763
37993
|
},
|
|
36764
37994
|
{
|
|
36765
37995
|
instructions: briefMcpInstructions
|
|
@@ -36859,6 +38089,7 @@ var init_server = __esm({
|
|
|
36859
38089
|
"small_refactor",
|
|
36860
38090
|
"bug_investigation",
|
|
36861
38091
|
"qa_edge_cases",
|
|
38092
|
+
"web_dogfood",
|
|
36862
38093
|
"performance",
|
|
36863
38094
|
"porting",
|
|
36864
38095
|
"experiment",
|
|
@@ -36899,14 +38130,20 @@ var init_server = __esm({
|
|
|
36899
38130
|
]);
|
|
36900
38131
|
registerTool(
|
|
36901
38132
|
"install_status",
|
|
36902
|
-
"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.",
|
|
36903
38134
|
{
|
|
36904
38135
|
repoPath: z2.string().optional().describe(
|
|
36905
38136
|
"Path inside the repo. Defaults to BRIEF_REPO_ROOT captured from the agent workspace, then current working directory."
|
|
36906
38137
|
),
|
|
36907
|
-
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()
|
|
36908
38145
|
},
|
|
36909
|
-
async ({ repoPath, detail }) => {
|
|
38146
|
+
async ({ repoPath, detail, task, files, branch, maxTokens }) => {
|
|
36910
38147
|
const rootResolution = resolveRepoRootDetails(repoPath);
|
|
36911
38148
|
const repoRoot = rootResolution.repoRoot;
|
|
36912
38149
|
const repoTarget = repoTargetHealth(rootResolution);
|
|
@@ -36930,8 +38167,16 @@ var init_server = __esm({
|
|
|
36930
38167
|
);
|
|
36931
38168
|
}
|
|
36932
38169
|
const { scan, teamContext } = await scanRepoWithTeamContext(repoRoot, {
|
|
36933
|
-
maxFiles:
|
|
38170
|
+
maxFiles: 1200
|
|
36934
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;
|
|
36935
38180
|
const report = buildLocalValueReport(repoRoot, scan.repoName);
|
|
36936
38181
|
const setups = generateAgentSetup(scan, { target: "all" });
|
|
36937
38182
|
const skillLearning = buildRepoSkillLearningReport(repoRoot, {
|
|
@@ -36958,6 +38203,7 @@ var init_server = __esm({
|
|
|
36958
38203
|
skillLearning: summarizeSkillLearningForStart(skillLearning, void 0),
|
|
36959
38204
|
connectorHealth: summarizeAgentConnectorHealth(setups),
|
|
36960
38205
|
proofLoop: report.proofLoop,
|
|
38206
|
+
...activation ? { activation } : {},
|
|
36961
38207
|
value: {
|
|
36962
38208
|
receiptCount: report.receiptCount,
|
|
36963
38209
|
contextPacketCount: report.contextPacketCount,
|
|
@@ -36970,8 +38216,8 @@ var init_server = __esm({
|
|
|
36970
38216
|
next: report.proofLoop.next
|
|
36971
38217
|
};
|
|
36972
38218
|
if (detail === "expanded") return jsonText(fullStatus);
|
|
36973
|
-
return jsonText(
|
|
36974
|
-
compactInstallStatus({
|
|
38219
|
+
return jsonText({
|
|
38220
|
+
...compactInstallStatus({
|
|
36975
38221
|
mode: fullStatus.mode,
|
|
36976
38222
|
repoRoot,
|
|
36977
38223
|
rootResolution,
|
|
@@ -36985,8 +38231,9 @@ var init_server = __esm({
|
|
|
36985
38231
|
workflowWiring,
|
|
36986
38232
|
valueReport: report,
|
|
36987
38233
|
serverInstructionsActive: true
|
|
36988
|
-
})
|
|
36989
|
-
|
|
38234
|
+
}),
|
|
38235
|
+
...activation ? { activation } : {}
|
|
38236
|
+
});
|
|
36990
38237
|
}
|
|
36991
38238
|
);
|
|
36992
38239
|
registerTool(
|
|
@@ -37214,9 +38461,12 @@ var init_server = __esm({
|
|
|
37214
38461
|
{
|
|
37215
38462
|
repoPath: z2.string().optional().describe(
|
|
37216
38463
|
"Path inside the repo. Defaults to BRIEF_REPO_ROOT captured from the agent workspace, then current working directory."
|
|
38464
|
+
),
|
|
38465
|
+
detail: z2.enum(["compact", "expanded"]).optional().describe(
|
|
38466
|
+
"Use compact by default for the agent loop; request expanded only when diagnosing wiring or setup."
|
|
37217
38467
|
)
|
|
37218
38468
|
},
|
|
37219
|
-
async ({ repoPath }) => {
|
|
38469
|
+
async ({ repoPath, detail }) => {
|
|
37220
38470
|
const repoTargetInfo = resolveRepoTarget(repoPath);
|
|
37221
38471
|
if (repoTargetInfo.repoTarget.status === "needs_repo_target") {
|
|
37222
38472
|
return repoTargetBlock(repoTargetInfo);
|
|
@@ -37226,14 +38476,19 @@ var init_server = __esm({
|
|
|
37226
38476
|
const skillLearning = buildRepoSkillLearningReport(repoRoot, {
|
|
37227
38477
|
maxFiles: 6e3
|
|
37228
38478
|
});
|
|
38479
|
+
const autoEngage = buildAgentAutoEngagementPlan(scan, skillLearning);
|
|
38480
|
+
const workflowWiring = evaluateAgentWorkflowWiring(scan);
|
|
38481
|
+
const responseDetail = detail ?? "compact";
|
|
37229
38482
|
return jsonText({
|
|
37230
38483
|
repoRoot,
|
|
37231
38484
|
rootResolution,
|
|
37232
38485
|
repoTarget,
|
|
37233
|
-
|
|
37234
|
-
|
|
38486
|
+
detail: responseDetail,
|
|
38487
|
+
autoEngage: responseDetail === "expanded" ? autoEngage : summarizeAutoEngageForStart(autoEngage),
|
|
38488
|
+
workflowWiring: responseDetail === "expanded" ? workflowWiring : summarizeWorkflowWiringForStart(workflowWiring),
|
|
37235
38489
|
skillLearning: summarizeSkillLearningForStart(skillLearning, void 0),
|
|
37236
|
-
next: "Install the default instruction into always-on agent guidance, then run the smoke test from the connected coding agent to prove Brief stays engaged from natural task asks."
|
|
38490
|
+
next: responseDetail === "expanded" ? "Install the default instruction into always-on agent guidance, then run the smoke test from the connected coding agent to prove Brief stays engaged from natural task asks." : "Install the default instruction, then give the connected agent a normal task and confirm it calls brief.next before editing. Request detail: expanded only to diagnose wiring.",
|
|
38491
|
+
detailHint: 'Call brief.auto_engage with detail: "expanded" only when diagnosing setup or workflow wiring.'
|
|
37237
38492
|
});
|
|
37238
38493
|
}
|
|
37239
38494
|
);
|
|
@@ -37352,7 +38607,7 @@ var init_server = __esm({
|
|
|
37352
38607
|
skills: scan.stats.skillCount
|
|
37353
38608
|
}
|
|
37354
38609
|
},
|
|
37355
|
-
next: teamContext.status === "fetched" ? "Use brief.context_for_task to route this team context into the next task." : "
|
|
38610
|
+
next: teamContext.status === "fetched" ? "Use brief.context_for_task to route this team context into the next task." : "Create a browser-authorized connection from the Brief app, then restart the agent. Approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID."
|
|
37356
38611
|
});
|
|
37357
38612
|
}
|
|
37358
38613
|
);
|
|
@@ -38127,7 +39382,7 @@ var init_server = __esm({
|
|
|
38127
39382
|
const sourceRepos = sourceRepoPaths && sourceRepoPaths.length > 0 ? sourceRepoPaths : [repoRoot];
|
|
38128
39383
|
const inventories = sourceRepos.map(
|
|
38129
39384
|
(sourceRepo) => buildAgentPackInventory(
|
|
38130
|
-
|
|
39385
|
+
path19.isAbsolute(sourceRepo) ? sourceRepo : path19.resolve(repoRoot, sourceRepo),
|
|
38131
39386
|
maxFiles === void 0 ? {} : { maxFiles }
|
|
38132
39387
|
)
|
|
38133
39388
|
);
|
|
@@ -38202,6 +39457,109 @@ var init_server = __esm({
|
|
|
38202
39457
|
});
|
|
38203
39458
|
}
|
|
38204
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
|
+
);
|
|
38205
39563
|
registerTool(
|
|
38206
39564
|
"context_for_task",
|
|
38207
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.",
|
|
@@ -39329,6 +40687,7 @@ var init_server = __esm({
|
|
|
39329
40687
|
"small_refactor",
|
|
39330
40688
|
"bug_investigation",
|
|
39331
40689
|
"qa_edge_cases",
|
|
40690
|
+
"web_dogfood",
|
|
39332
40691
|
"performance",
|
|
39333
40692
|
"porting",
|
|
39334
40693
|
"experiment",
|
|
@@ -39502,6 +40861,7 @@ init_dist2();
|
|
|
39502
40861
|
init_teamContext();
|
|
39503
40862
|
init_missionInputOptions();
|
|
39504
40863
|
init_cloud();
|
|
40864
|
+
init_cloudCredentials();
|
|
39505
40865
|
init_workspace();
|
|
39506
40866
|
init_connectorReadiness();
|
|
39507
40867
|
init_connectorRecovery();
|
|
@@ -39513,15 +40873,140 @@ init_wikiInit();
|
|
|
39513
40873
|
init_handoffAutofill();
|
|
39514
40874
|
init_mapResponse();
|
|
39515
40875
|
init_scanFreshness();
|
|
39516
|
-
import { chmodSync as
|
|
39517
|
-
import
|
|
40876
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
40877
|
+
import path20 from "node:path";
|
|
39518
40878
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
40879
|
+
|
|
40880
|
+
// ../mcp/src/runner.ts
|
|
40881
|
+
init_cloud();
|
|
40882
|
+
import { hostname } from "node:os";
|
|
40883
|
+
import { spawn } from "node:child_process";
|
|
40884
|
+
async function runLoopRunner(options2, log = console.log) {
|
|
40885
|
+
const runnerId = options2.runnerId ?? `${hostname()}-${process.pid}`;
|
|
40886
|
+
const intervalMs = Math.max(options2.intervalMs ?? 4e3, 1e3);
|
|
40887
|
+
const env = options2.env ?? process.env;
|
|
40888
|
+
let didRun = false;
|
|
40889
|
+
do {
|
|
40890
|
+
const run = await claimRemoteLoopRun(runnerId, 180, env);
|
|
40891
|
+
if (!run) {
|
|
40892
|
+
if (options2.once) return;
|
|
40893
|
+
await sleep(intervalMs);
|
|
40894
|
+
continue;
|
|
40895
|
+
}
|
|
40896
|
+
didRun = true;
|
|
40897
|
+
log({ status: "running", runId: run.id, loop: run.loopName });
|
|
40898
|
+
try {
|
|
40899
|
+
const result = await executeAgentCommand(
|
|
40900
|
+
options2.agentCommand,
|
|
40901
|
+
run,
|
|
40902
|
+
options2.repoRoot,
|
|
40903
|
+
options2.commandTimeoutMs ?? 30 * 60 * 1e3
|
|
40904
|
+
);
|
|
40905
|
+
const proof = {
|
|
40906
|
+
runnerId,
|
|
40907
|
+
exitCode: result.exitCode,
|
|
40908
|
+
stdoutBytes: Buffer.byteLength(result.stdout, "utf8"),
|
|
40909
|
+
stderrBytes: Buffer.byteLength(result.stderr, "utf8"),
|
|
40910
|
+
completedLocallyAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40911
|
+
};
|
|
40912
|
+
const updated = await updateRemoteLoopRun(run.id, {
|
|
40913
|
+
runnerId,
|
|
40914
|
+
status: result.exitCode === 0 ? "succeeded" : "failed",
|
|
40915
|
+
lastEvent: result.exitCode === 0 ? "The local agent completed the loop." : "The local agent exited with a failure.",
|
|
40916
|
+
resultSummary: compactOutput(result.stdout || result.stderr),
|
|
40917
|
+
proof,
|
|
40918
|
+
...result.exitCode === 0 ? {} : { errorMessage: compactOutput(result.stderr || result.stdout) }
|
|
40919
|
+
}, env);
|
|
40920
|
+
log({ status: updated.status, runId: updated.id, proof });
|
|
40921
|
+
} catch (caught) {
|
|
40922
|
+
const message = caught instanceof Error ? caught.message : "Local runner failed.";
|
|
40923
|
+
try {
|
|
40924
|
+
const updated = await updateRemoteLoopRun(run.id, {
|
|
40925
|
+
runnerId,
|
|
40926
|
+
status: "failed",
|
|
40927
|
+
lastEvent: "The local runner could not execute the agent command.",
|
|
40928
|
+
errorMessage: message,
|
|
40929
|
+
proof: { runnerId, completedLocallyAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
40930
|
+
}, env);
|
|
40931
|
+
log({ status: updated.status, runId: updated.id, error: message });
|
|
40932
|
+
} catch (updateError) {
|
|
40933
|
+
log({ status: "failed", runId: run.id, error: message, updateError });
|
|
40934
|
+
throw new Error(
|
|
40935
|
+
`The runner could not persist the failed loop state: ${updateError instanceof Error ? updateError.message : "unknown update error"}`
|
|
40936
|
+
);
|
|
40937
|
+
}
|
|
40938
|
+
}
|
|
40939
|
+
} while (!options2.once || !didRun);
|
|
40940
|
+
}
|
|
40941
|
+
async function executeAgentCommand(command2, run, repoRoot, timeoutMs) {
|
|
40942
|
+
const prompt = [
|
|
40943
|
+
`You are running the approved Brief team loop: ${run.loopName}.`,
|
|
40944
|
+
`Run id: ${run.id}`,
|
|
40945
|
+
`Repository: ${run.repoFullName ?? repoRoot}`,
|
|
40946
|
+
"",
|
|
40947
|
+
"Approved loop guidance:",
|
|
40948
|
+
run.loopBody,
|
|
40949
|
+
"",
|
|
40950
|
+
"Guardrails:",
|
|
40951
|
+
...run.guardrails.length > 0 ? run.guardrails.map((item) => `- ${item}`) : ["- Stop before external side effects without approval."],
|
|
40952
|
+
"",
|
|
40953
|
+
"Task:",
|
|
40954
|
+
run.task,
|
|
40955
|
+
"",
|
|
40956
|
+
"Return a concise summary of what you changed, validation run, proof, and remaining risk."
|
|
40957
|
+
].join("\n");
|
|
40958
|
+
return await new Promise((resolve, reject) => {
|
|
40959
|
+
const child = spawn("/bin/sh", ["-lc", command2], {
|
|
40960
|
+
cwd: repoRoot,
|
|
40961
|
+
env: process.env,
|
|
40962
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
40963
|
+
});
|
|
40964
|
+
let stdout = "";
|
|
40965
|
+
let stderr = "";
|
|
40966
|
+
const timer = setTimeout(() => {
|
|
40967
|
+
child.kill("SIGTERM");
|
|
40968
|
+
reject(new Error(`Agent command timed out after ${Math.round(timeoutMs / 6e4)} minutes.`));
|
|
40969
|
+
}, timeoutMs);
|
|
40970
|
+
child.stdout.on("data", (chunk) => {
|
|
40971
|
+
stdout = `${stdout}${chunk.toString()}`.slice(-6e4);
|
|
40972
|
+
});
|
|
40973
|
+
child.stderr.on("data", (chunk) => {
|
|
40974
|
+
stderr = `${stderr}${chunk.toString()}`.slice(-2e4);
|
|
40975
|
+
});
|
|
40976
|
+
child.on("error", (error) => {
|
|
40977
|
+
clearTimeout(timer);
|
|
40978
|
+
reject(error);
|
|
40979
|
+
});
|
|
40980
|
+
child.on("close", (code) => {
|
|
40981
|
+
clearTimeout(timer);
|
|
40982
|
+
resolve({ exitCode: code ?? 1, stdout, stderr });
|
|
40983
|
+
});
|
|
40984
|
+
child.stdin.end(prompt);
|
|
40985
|
+
});
|
|
40986
|
+
}
|
|
40987
|
+
function compactOutput(value) {
|
|
40988
|
+
return value.trim().slice(-12e3) || "The agent returned no summary.";
|
|
40989
|
+
}
|
|
40990
|
+
function sleep(ms) {
|
|
40991
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
40992
|
+
}
|
|
40993
|
+
|
|
40994
|
+
// ../mcp/src/cli.ts
|
|
39519
40995
|
var args = process.argv.slice(2);
|
|
39520
40996
|
var command = args[0];
|
|
39521
40997
|
var cliStartedAt = /* @__PURE__ */ new Date();
|
|
39522
40998
|
var cliModulePath = fileURLToPath2(import.meta.url);
|
|
40999
|
+
if (!isCloudLogoutCommand(command)) {
|
|
41000
|
+
const cloudBootstrap2 = await bootstrapCloudCredentials();
|
|
41001
|
+
process.env.BRIEF_CLOUD_BOOTSTRAP_DONE = "1";
|
|
41002
|
+
if (cloudBootstrap2.status === "failed") {
|
|
41003
|
+
console.error(
|
|
41004
|
+
`Brief team sync is unavailable: ${cloudBootstrap2.reason} Continuing in local mode.`
|
|
41005
|
+
);
|
|
41006
|
+
}
|
|
41007
|
+
}
|
|
39523
41008
|
if (!command || command === "--stdio" || command === "stdio" || command === "mcp") {
|
|
39524
|
-
await
|
|
41009
|
+
await init_server().then(() => server_exports);
|
|
39525
41010
|
} else {
|
|
39526
41011
|
await runCli(command, args.slice(1));
|
|
39527
41012
|
}
|
|
@@ -39530,11 +41015,41 @@ async function runCli(commandName, commandArgs) {
|
|
|
39530
41015
|
printJson(buildCliHelp(commandName));
|
|
39531
41016
|
return;
|
|
39532
41017
|
}
|
|
41018
|
+
if (isCloudLogoutCommand(commandName)) {
|
|
41019
|
+
forgetCloudCredentials();
|
|
41020
|
+
printJson({
|
|
41021
|
+
status: "cloud_credentials_removed",
|
|
41022
|
+
path: defaultCredentialsPath(),
|
|
41023
|
+
next: "Create a fresh browser-authorized connection from the Brief app before using team sync again."
|
|
41024
|
+
});
|
|
41025
|
+
return;
|
|
41026
|
+
}
|
|
39533
41027
|
const rootResolution = resolveRepoRootDetails(
|
|
39534
41028
|
option2(commandArgs, "repo") ?? option2(commandArgs, "repoPath") ?? option2(commandArgs, "repo-path")
|
|
39535
41029
|
);
|
|
39536
41030
|
const repoRoot = rootResolution.repoRoot;
|
|
39537
41031
|
const repoTarget = repoTargetHealth(rootResolution);
|
|
41032
|
+
if (isCommand(commandName, "runner", "loop-runner", "run-loop")) {
|
|
41033
|
+
const agentCommand = option2(commandArgs, "agent-command") ?? process.env.BRIEF_AGENT_COMMAND;
|
|
41034
|
+
if (!agentCommand) {
|
|
41035
|
+
fail(
|
|
41036
|
+
"Missing --agent-command. Set BRIEF_AGENT_COMMAND to the local Claude, Codex, or compatible agent command."
|
|
41037
|
+
);
|
|
41038
|
+
}
|
|
41039
|
+
const runnerOptions = {
|
|
41040
|
+
repoRoot,
|
|
41041
|
+
agentCommand,
|
|
41042
|
+
once: hasFlag(commandArgs, "once")
|
|
41043
|
+
};
|
|
41044
|
+
const runnerId = option2(commandArgs, "runner-id");
|
|
41045
|
+
const intervalMs = numberOption(commandArgs, "interval-ms");
|
|
41046
|
+
const commandTimeoutMs = numberOption(commandArgs, "timeout-ms");
|
|
41047
|
+
if (runnerId) runnerOptions.runnerId = runnerId;
|
|
41048
|
+
if (intervalMs) runnerOptions.intervalMs = intervalMs;
|
|
41049
|
+
if (commandTimeoutMs) runnerOptions.commandTimeoutMs = commandTimeoutMs;
|
|
41050
|
+
await runLoopRunner(runnerOptions);
|
|
41051
|
+
return;
|
|
41052
|
+
}
|
|
39538
41053
|
if (isCommand(commandName, "install-status", "install_status")) {
|
|
39539
41054
|
const freshness = cliServerFreshness();
|
|
39540
41055
|
const connector = connectorReadiness({
|
|
@@ -39667,6 +41182,66 @@ async function runCli(commandName, commandArgs) {
|
|
|
39667
41182
|
process.exitCode = 1;
|
|
39668
41183
|
return;
|
|
39669
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
|
+
}
|
|
39670
41245
|
if (isCommand(
|
|
39671
41246
|
commandName,
|
|
39672
41247
|
"pre-pr",
|
|
@@ -40200,7 +41775,7 @@ async function runCli(commandName, commandArgs) {
|
|
|
40200
41775
|
const sourceRepos = filesOption2(commandArgs, "source-repos") ?? filesOption2(commandArgs, "sourceRepos") ?? filesOption2(commandArgs, "sources") ?? [repoRoot];
|
|
40201
41776
|
const inventories = sourceRepos.map(
|
|
40202
41777
|
(sourceRepo) => buildAgentPackInventory(
|
|
40203
|
-
|
|
41778
|
+
path20.isAbsolute(sourceRepo) ? sourceRepo : path20.resolve(repoRoot, sourceRepo),
|
|
40204
41779
|
maxFiles === void 0 ? {} : { maxFiles }
|
|
40205
41780
|
)
|
|
40206
41781
|
);
|
|
@@ -41072,7 +42647,7 @@ async function runCli(commandName, commandArgs) {
|
|
|
41072
42647
|
skills: scan.stats.skillCount
|
|
41073
42648
|
}
|
|
41074
42649
|
},
|
|
41075
|
-
next: teamContext.status === "fetched" ? "Use context_for_task to route this team context into the next task." : "
|
|
42650
|
+
next: teamContext.status === "fetched" ? "Use context_for_task to route this team context into the next task." : "Create a browser-authorized connection from the Brief app, then restart the agent. Approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID."
|
|
41076
42651
|
});
|
|
41077
42652
|
return;
|
|
41078
42653
|
}
|
|
@@ -41269,6 +42844,11 @@ async function runCli(commandName, commandArgs) {
|
|
|
41269
42844
|
function isCommand(commandName, ...aliases) {
|
|
41270
42845
|
return aliases.includes(commandName);
|
|
41271
42846
|
}
|
|
42847
|
+
function isCloudLogoutCommand(commandName) {
|
|
42848
|
+
return Boolean(
|
|
42849
|
+
commandName && ["cloud-logout", "cloud_logout", "disconnect-cloud"].includes(commandName)
|
|
42850
|
+
);
|
|
42851
|
+
}
|
|
41272
42852
|
function wikiInitMode(argsList) {
|
|
41273
42853
|
if (hasFlag(argsList, "init")) return "init";
|
|
41274
42854
|
if (hasFlag(argsList, "update")) return "update";
|
|
@@ -41352,6 +42932,33 @@ function cliCommandHelp() {
|
|
|
41352
42932
|
"After changing Brief source to confirm the running connector is fresh."
|
|
41353
42933
|
]
|
|
41354
42934
|
},
|
|
42935
|
+
{
|
|
42936
|
+
command: "cloud-logout",
|
|
42937
|
+
aliases: ["cloud_logout", "disconnect-cloud"],
|
|
42938
|
+
purpose: "Remove the locally stored cloud credential so the next setup can pair a device again.",
|
|
42939
|
+
repoBound: false,
|
|
42940
|
+
usage: "brief cloud-logout",
|
|
42941
|
+
examples: ["brief cloud-logout"],
|
|
42942
|
+
whenToUse: [
|
|
42943
|
+
"When a device needs to be disconnected from a Brief workspace.",
|
|
42944
|
+
"When a one-time browser pairing has expired before the connector started."
|
|
42945
|
+
]
|
|
42946
|
+
},
|
|
42947
|
+
{
|
|
42948
|
+
command: "runner",
|
|
42949
|
+
aliases: ["loop-runner", "run-loop"],
|
|
42950
|
+
purpose: "Poll for an approved team loop and execute it with the developer's local agent command.",
|
|
42951
|
+
repoBound: true,
|
|
42952
|
+
usage: 'runbrief runner --repo $PWD --agent-command "claude -p"',
|
|
42953
|
+
examples: [
|
|
42954
|
+
'runbrief runner --repo $PWD --agent-command "claude -p"',
|
|
42955
|
+
'runbrief runner --repo $PWD --agent-command "codex exec" --once'
|
|
42956
|
+
],
|
|
42957
|
+
whenToUse: [
|
|
42958
|
+
"After pairing a connector from the Brief app.",
|
|
42959
|
+
"When a team-approved loop is queued in the web app."
|
|
42960
|
+
]
|
|
42961
|
+
},
|
|
41355
42962
|
{
|
|
41356
42963
|
command: "next",
|
|
41357
42964
|
aliases: ["brief-next", "brief_next"],
|
|
@@ -41438,6 +43045,36 @@ function cliCommandHelp() {
|
|
|
41438
43045
|
"When the agent needs acceptance criteria and state checks before editing."
|
|
41439
43046
|
]
|
|
41440
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
|
+
},
|
|
41441
43078
|
{
|
|
41442
43079
|
command: "map",
|
|
41443
43080
|
aliases: ["codebase-map", "codebase_map"],
|
|
@@ -41696,8 +43333,21 @@ function positionalText(argsList) {
|
|
|
41696
43333
|
"doNotCopy",
|
|
41697
43334
|
"owner",
|
|
41698
43335
|
"receipts",
|
|
43336
|
+
"audience",
|
|
43337
|
+
"competitor",
|
|
43338
|
+
"competitors",
|
|
43339
|
+
"integration",
|
|
43340
|
+
"integrations",
|
|
43341
|
+
"facts-file",
|
|
43342
|
+
"repo-name",
|
|
43343
|
+
"open-questions",
|
|
43344
|
+
"decisions",
|
|
41699
43345
|
"added-by",
|
|
41700
|
-
"addedBy"
|
|
43346
|
+
"addedBy",
|
|
43347
|
+
"agent-command",
|
|
43348
|
+
"runner-id",
|
|
43349
|
+
"interval-ms",
|
|
43350
|
+
"timeout-ms"
|
|
41701
43351
|
]);
|
|
41702
43352
|
const parts = [];
|
|
41703
43353
|
for (let index = 0; index < argsList.length; index += 1) {
|
|
@@ -41814,6 +43464,7 @@ function recipeOption(argsList) {
|
|
|
41814
43464
|
"small_refactor",
|
|
41815
43465
|
"bug_investigation",
|
|
41816
43466
|
"qa_edge_cases",
|
|
43467
|
+
"web_dogfood",
|
|
41817
43468
|
"performance",
|
|
41818
43469
|
"porting",
|
|
41819
43470
|
"experiment",
|
|
@@ -41953,7 +43604,7 @@ function artifactEvidenceFromOptions(argsList) {
|
|
|
41953
43604
|
);
|
|
41954
43605
|
const evidence = [];
|
|
41955
43606
|
for (const raw of rawEvidenceValues) {
|
|
41956
|
-
const json = raw.startsWith("@") ?
|
|
43607
|
+
const json = raw.startsWith("@") ? readFileSync7(path20.resolve(raw.slice(1)), "utf8") : raw;
|
|
41957
43608
|
try {
|
|
41958
43609
|
const parsed = JSON.parse(json);
|
|
41959
43610
|
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
@@ -42031,7 +43682,7 @@ function missionSummaryFromLocalReceipt2(repoRoot, missionReceiptId) {
|
|
|
42031
43682
|
function runtimeEvidencePacketFromOptions(argsList) {
|
|
42032
43683
|
const raw = option2(argsList, "runtime-packet") ?? option2(argsList, "runtime-evidence-packet") ?? option2(argsList, "runtimeEvidencePacket");
|
|
42033
43684
|
if (!raw) return void 0;
|
|
42034
|
-
const json = raw.startsWith("@") ?
|
|
43685
|
+
const json = raw.startsWith("@") ? readFileSync7(path20.resolve(raw.slice(1)), "utf8") : raw;
|
|
42035
43686
|
try {
|
|
42036
43687
|
return cleanRuntimeEvidencePacket2(
|
|
42037
43688
|
runtimeEvidencePacketSchema.parse(JSON.parse(json))
|
|
@@ -42200,6 +43851,15 @@ function printContextSummary(packet, localReceipt, teamContext) {
|
|
|
42200
43851
|
teamContextLine(teamContext),
|
|
42201
43852
|
teamGuidanceLine(packet),
|
|
42202
43853
|
valueSignalLine(packet),
|
|
43854
|
+
...packet.dependencyRemediation ? [
|
|
43855
|
+
"",
|
|
43856
|
+
"Dependency plan:",
|
|
43857
|
+
`- ${packet.dependencyRemediation.packageManager} \xB7 ${packet.dependencyRemediation.status}`,
|
|
43858
|
+
`- manifests: ${packet.dependencyRemediation.manifestFiles.slice(0, 4).join(", ") || "none found"}`,
|
|
43859
|
+
`- lockfiles: ${packet.dependencyRemediation.lockfiles.slice(0, 3).join(", ") || "none found"}`,
|
|
43860
|
+
`- packages: ${packet.dependencyRemediation.packageSignals.map((item) => item.name).join(", ") || "need advisory/scanner evidence"}`,
|
|
43861
|
+
`- graph proof: ${packet.dependencyRemediation.graphCommands.slice(0, 2).join(", ") || "name the direct/transitive path"}`
|
|
43862
|
+
] : [],
|
|
42203
43863
|
"",
|
|
42204
43864
|
"Rules:",
|
|
42205
43865
|
...packet.rules.length > 0 ? packet.rules.slice(0, 8).map((rule) => `- [${rule.enforcement}] ${rule.title}`) : ["- none"],
|
|
@@ -42252,10 +43912,10 @@ function valueSignalLine(packet) {
|
|
|
42252
43912
|
function writeHookFiles2(repoRoot, files) {
|
|
42253
43913
|
const written = [];
|
|
42254
43914
|
for (const file of files) {
|
|
42255
|
-
const targetPath =
|
|
42256
|
-
|
|
42257
|
-
|
|
42258
|
-
if (file.path.endsWith(".sh"))
|
|
43915
|
+
const targetPath = path20.join(repoRoot, file.path);
|
|
43916
|
+
mkdirSync5(path20.dirname(targetPath), { recursive: true });
|
|
43917
|
+
writeFileSync8(targetPath, file.body);
|
|
43918
|
+
if (file.path.endsWith(".sh")) chmodSync3(targetPath, 493);
|
|
42259
43919
|
written.push(file.path);
|
|
42260
43920
|
}
|
|
42261
43921
|
return written;
|
|
@@ -42383,7 +44043,8 @@ function cliServerFreshness() {
|
|
|
42383
44043
|
return mcpServerFreshness({
|
|
42384
44044
|
serverStartedAt: cliStartedAt,
|
|
42385
44045
|
entrypoint: cliModulePath,
|
|
42386
|
-
mode: "cli"
|
|
44046
|
+
mode: "cli",
|
|
44047
|
+
distribution: connectorDistributionFor(cliModulePath)
|
|
42387
44048
|
});
|
|
42388
44049
|
}
|
|
42389
44050
|
function printJson(value) {
|
|
@@ -42407,7 +44068,7 @@ function nextScanStep(cloudSync) {
|
|
|
42407
44068
|
if (cloudSync.status === "failed") {
|
|
42408
44069
|
return `The local scan succeeded, but the web app did not update because cloud sync failed: ${cloudSync.reason}. Fix cloud sync before trusting hosted freshness; refresh local wiki separately with brief.initialize_repo_wiki update: true.`;
|
|
42409
44070
|
}
|
|
42410
|
-
return "The local scan succeeded, but the web app did not update.
|
|
44071
|
+
return "The local scan succeeded, but the web app did not update. Create a browser-authorized connection from the Brief app and restart the agent; approved legacy setups can provide BRIEF_API_URL, BRIEF_API_KEY, and BRIEF_WORKSPACE_ID. To refresh repo-local wiki docs, ask the agent to call brief.initialize_repo_wiki with update: true.";
|
|
42411
44072
|
}
|
|
42412
44073
|
function fail(message) {
|
|
42413
44074
|
process.stderr.write(`${message}
|