@productbrain/mcp 0.0.1-beta.64 → 0.0.1-beta.66

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.
@@ -3,6 +3,7 @@ import {
3
3
  captureSchema,
4
4
  checkEntryQuality,
5
5
  classifyCollection,
6
+ clearSessionGaps,
6
7
  closeAgentSession,
7
8
  deriveEpistemicStatus,
8
9
  extractPreview,
@@ -14,6 +15,8 @@ import {
14
15
  getAgentSessionId,
15
16
  getApiKeyScope,
16
17
  getAuditLog,
18
+ getSessionGaps,
19
+ getTopGaps,
17
20
  getWorkspaceContext,
18
21
  getWorkspaceId,
19
22
  initToolSurface,
@@ -23,6 +26,7 @@ import {
23
26
  mcpQuery,
24
27
  notFoundResult,
25
28
  parseOrFail,
29
+ recordGap,
26
30
  recordSessionActivity,
27
31
  registerSmartCaptureTools,
28
32
  requireActiveSession,
@@ -38,7 +42,7 @@ import {
38
42
  unknownAction,
39
43
  validationResult,
40
44
  withEnvelope
41
- } from "./chunk-DM5DZC7B.js";
45
+ } from "./chunk-UWILSX73.js";
42
46
  import {
43
47
  trackKnowledgeGap,
44
48
  trackQualityCheck,
@@ -233,7 +237,7 @@ ${formatted}` }],
233
237
  },
234
238
  withEnvelope(async ({ entryId }) => {
235
239
  requireWriteAccess();
236
- const { runContradictionCheck } = await import("./smart-capture-NYOKGAFS.js");
240
+ const { runContradictionCheck } = await import("./smart-capture-EGTUM4XP.js");
237
241
  const entry = await mcpQuery("chain.getEntry", { entryId });
238
242
  if (!entry) {
239
243
  return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
@@ -353,43 +357,6 @@ ${formatted}` }],
353
357
 
354
358
  // src/tools/entries.ts
355
359
  import { z as z2 } from "zod";
356
-
357
- // src/gap-store.ts
358
- var SESSION_GAPS = [];
359
- var DEDUP_WINDOW_MS = 6e4;
360
- function isDuplicate(gap) {
361
- const cutoff = gap.timestamp - DEDUP_WINDOW_MS;
362
- return SESSION_GAPS.some(
363
- (existing) => existing.query === gap.query && existing.tool === gap.tool && existing.action === gap.action && existing.timestamp >= cutoff
364
- );
365
- }
366
- function recordGap(gap) {
367
- const timestamped = { ...gap, timestamp: Date.now() };
368
- if (isDuplicate(timestamped)) return false;
369
- SESSION_GAPS.push(timestamped);
370
- return true;
371
- }
372
- function getSessionGaps() {
373
- return SESSION_GAPS;
374
- }
375
- function getTopGaps(limit = 5) {
376
- const counts = /* @__PURE__ */ new Map();
377
- for (const gap of SESSION_GAPS) {
378
- const key = gap.query.toLowerCase().trim();
379
- const existing = counts.get(key);
380
- if (existing) {
381
- existing.count++;
382
- } else {
383
- counts.set(key, { count: 1, gapType: gap.gapType });
384
- }
385
- }
386
- return [...counts.entries()].map(([query, { count, gapType }]) => ({ query, count, gapType })).sort((a, b) => b.count - a.count).slice(0, limit);
387
- }
388
- function clearSessionGaps() {
389
- SESSION_GAPS.length = 0;
390
- }
391
-
392
- // src/tools/entries.ts
393
360
  function sanitizeEntryData(data) {
394
361
  if (!data || typeof data !== "object") return void 0;
395
362
  const filtered = Object.fromEntries(
@@ -3199,6 +3166,7 @@ var IMPLEMENTATION_REVIEW_WORKFLOW_DESCRIPTOR = {
3199
3166
  ## Review scope (CRITICAL)
3200
3167
 
3201
3168
  - **Default scope is this conversation only.** Only the work discussed or produced in this conversation is in scope. Do not review or touch files, BETs, or work outside that scope unless the user explicitly expands it (e.g. "also review the auth module").
3169
+ - **Every file touched must be explicit.** Scope is not complete until you have listed every file that was touched (created, modified, or deleted) in the work under review. Use git status/diff and conversation context to enumerate them. The review then assesses holistic coherence: does this set of changes fit our system, bring us closer to our vision, and make our code and architecture cleaner?
3202
3170
  - **Infer scope first.** Use conversation context, git status/diff, and recent edits to determine which BET, feature, or files are in scope. Call orient/start and \`context action=gather\` with the inferred scope; do not ask the user to pick what to review unless it's ambiguous.
3203
3171
  - **Ask only to clarify boundaries.** If scope is ambiguous after inference, ask a single, focused question to clarify (e.g. "Should this review include only the changes we just made, or the whole feature?"). Do not ask open-ended "what should we review?" or "which work area?" unless inference failed.
3204
3172
  - **State scope explicitly in Round 01.** After orient and context gather, state in one sentence what is in scope (e.g. "Scope: changes in this conversation for BET-72" or "Scope: files X, Y and BET-72"). Confirm only if the user might reasonably expect something else.
@@ -3233,11 +3201,11 @@ When reviewing test results: (a) test staleness \u2014 code changed, test not up
3233
3201
  num: "01",
3234
3202
  label: "Orient & Scope",
3235
3203
  type: "open",
3236
- instruction: "Apply the Review scope rules: infer scope from this conversation, git diff, and recent edits (default = work in this conversation only). Call orient or start, then context action=gather for the inferred BET/feature. State scope in one explicit sentence. Only ask the participant if scope is ambiguous.",
3237
- facilitatorGuidance: "Infer scope from conversation context, git status/diff, and recent edits \u2014 default is work in this conversation only. Call orient or start to load governance and active bets. Use context action=gather task='implementation review for [inferred-BET-or-feature]' to load related entries. State scope explicitly in one sentence (e.g. 'Scope: changes in this conversation for BET-72'). Present: stated scope, relevant BETs/DECs/BRs, and any stale entries orient surfaces. Ask the participant only if scope is ambiguous (e.g. 'Only the files we changed, or the whole feature?').",
3204
+ instruction: "Apply the Review scope rules: infer scope from this conversation, git diff, and recent edits (default = work in this conversation only). Enumerate every file touched (created, modified, deleted). Call orient or start, then context action=gather for the inferred BET/feature. State scope in one explicit sentence and list all files in scope. Only ask the participant if scope is ambiguous. The review will assess: holistic coherence with our system, whether this brings us closer to our vision, and whether it makes our code and architecture cleaner.",
3205
+ facilitatorGuidance: "Infer scope from conversation context, git status/diff, and recent edits \u2014 default is work in this conversation only. List every file touched (created, modified, or deleted) in the work under review; scope is not complete without this list. Call orient or start to load governance and active bets. Use context action=gather task='implementation review for [inferred-BET-or-feature]' to load related entries. State scope explicitly in one sentence (e.g. 'Scope: changes in this conversation for BET-72'). Present: stated scope, the full list of files touched, relevant BETs/DECs/BRs, and any stale entries orient surfaces. Frame the review: we will assess holistic coherence with our system, whether this brings us closer to our vision, and whether it makes our code and architecture cleaner. Ask the participant only if scope is ambiguous (e.g. 'Only the files we changed, or the whole feature?').",
3238
3206
  outputSchema: {
3239
3207
  field: "scope",
3240
- description: "Explicit scope statement plus BET/feature IDs and related Chain context",
3208
+ description: "Explicit scope statement, every file touched, BET/feature IDs and related Chain context",
3241
3209
  format: "structured"
3242
3210
  },
3243
3211
  maxDurationHint: "3 min"
@@ -3289,8 +3257,8 @@ When reviewing test results: (a) test staleness \u2014 code changed, test not up
3289
3257
  num: "05",
3290
3258
  label: "Synthesis & Sign-Off",
3291
3259
  type: "commit",
3292
- instruction: "Summarize the review. Refactoring needed? Ship or conditional? End with BET/chain IDs.",
3293
- facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation. Recommend: ship, ship with conditions, or do not ship. CRITICAL: The output MUST end with one sentence: 'BET/feature IDs reviewed: [IDs]. [One-line summary].' Example: 'BET-xxx, STD-xxx reviewed. Conditional ship \u2014 fix [specific issues] and add [missing tests].'",
3260
+ instruction: "Summarize the review. Answer: Does this bring us closer to our vision and make our code and architecture cleaner? Refactoring needed? Ship or conditional? End with BET/chain IDs.",
3261
+ facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation. Explicitly answer: Does this bring us closer to our vision and make our code and architecture cleaner? Recommend: ship, ship with conditions, or do not ship. CRITICAL: The output MUST end with one sentence: 'BET/feature IDs reviewed: [IDs]. [One-line summary].' Example: 'BET-xxx, STD-xxx reviewed. Conditional ship \u2014 fix [specific issues] and add [missing tests].'",
3294
3262
  outputSchema: {
3295
3263
  field: "synthesis",
3296
3264
  description: "Full review synthesis with BET/chain IDs",
@@ -6305,7 +6273,7 @@ async function handleCommitConstellation(args) {
6305
6273
  }
6306
6274
  let contradictionWarnings = [];
6307
6275
  try {
6308
- const { runContradictionCheck } = await import("./smart-capture-NYOKGAFS.js");
6276
+ const { runContradictionCheck } = await import("./smart-capture-EGTUM4XP.js");
6309
6277
  const descField = betData.problem ?? betData.description ?? "";
6310
6278
  contradictionWarnings = await runContradictionCheck(
6311
6279
  betEntry.name ?? betId,
@@ -6966,23 +6934,32 @@ function buildPlannedWorkSection(work, priorSessions, recoveryBlock) {
6966
6934
  // src/tools/orient-shared.ts
6967
6935
  function runAlignmentCheck(task, activeBets, taskContextHits) {
6968
6936
  const betNames = activeBets.map((b) => b.name);
6969
- const taskWords = task.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
6937
+ const taskWords = task.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
6970
6938
  const matchingBet = activeBets.find((b) => {
6971
6939
  const name = (b.name ?? "").toLowerCase();
6972
6940
  return taskWords.some((w) => name.includes(w));
6973
6941
  });
6942
+ if (matchingBet) {
6943
+ return { aligned: true, matchedBet: matchingBet.name, matchSource: "active_bet", betNames };
6944
+ }
6974
6945
  const betHits = (taskContextHits ?? []).filter(
6975
6946
  (e) => e.collectionSlug === "bets"
6976
6947
  );
6977
- const aligned = !!matchingBet || betHits.length > 0;
6978
- const matchedBet = matchingBet?.name ?? betHits[0]?.name ?? null;
6979
- return { aligned, matchedBet, betNames };
6948
+ if (betHits.length > 0) {
6949
+ return { aligned: true, matchedBet: betHits[0]?.name ?? null, matchSource: "task_context", betNames };
6950
+ }
6951
+ return { aligned: false, matchedBet: null, matchSource: null, betNames };
6980
6952
  }
6981
6953
  function buildAlignmentCheckLines(result) {
6982
6954
  const lines = ["## Alignment Check"];
6983
- if (result.aligned) {
6955
+ if (result.aligned && result.matchSource === "active_bet") {
6984
6956
  lines.push(
6985
- `Task appears related to active bet: **${result.matchedBet}**. Proceed.`
6957
+ `Task aligns with active bet: **${result.matchedBet}**. Proceed.`
6958
+ );
6959
+ } else if (result.aligned && result.matchSource === "task_context") {
6960
+ const noActiveBets = result.betNames.length === 0;
6961
+ lines.push(
6962
+ `Task related to **${result.matchedBet}** via task context${noActiveBets ? " (no active bets in horizon=now)" : ""}. Proceed with caution \u2014 confirm scope with the user if uncertain.`
6986
6963
  );
6987
6964
  } else if (result.betNames.length === 0) {
6988
6965
  lines.push(
@@ -6998,16 +6975,18 @@ function buildAlignmentCheckLines(result) {
6998
6975
  lines.push("");
6999
6976
  return lines;
7000
6977
  }
7001
- var CORE_PROTOCOL = [
6978
+ var CORE_PROTOCOL_BASE = [
7002
6979
  '**Search before proposing.** Before suggesting new features, architecture, or changes, search the Chain: `entries action=search query="<relevant terms>"`. Build on what exists.',
7003
6980
  "**Reference by ID.** When discussing a topic that has Chain entries, cite them by entry ID (e.g. `DEC-50`, `PRI-3`). This keeps conversations grounded in shared knowledge.",
7004
6981
  "**Check scope.** If the proposed work doesn't fall under an active bet, stop and say so. Do not design implementation for out-of-scope work without explicit user go-ahead.",
7005
- `**Capture continuously.** When a decision, tension, insight, or new term surfaces during work, capture it as a draft immediately. Don't defer to "later."`,
7006
- "**Validate against governance.** Before proposing any solution, check it against the Workspace Governance directives below. If your proposal conflicts with a principle, standard, or business rule \u2014 stop, name the conflict, and get explicit user confirmation before proceeding."
6982
+ `**Capture continuously.** When a decision, tension, insight, or new term surfaces during work, capture it as a draft immediately. Don't defer to "later."`
7007
6983
  ];
6984
+ var RULE5_WITH_GOVERNANCE = "**Validate against governance.** Before proposing any solution, check it against the Workspace Governance directives below. If your proposal conflicts with a principle, standard, or business rule \u2014 stop, name the conflict, and get explicit user confirmation before proceeding.";
6985
+ var RULE5_COMPACT = '**Validate against governance.** Before proposing or building anything, call `orient task="<your task>"` to load relevant governance. If your proposal conflicts with a principle, standard, or business rule \u2014 stop, name the conflict, and get explicit user confirmation.';
7008
6986
  var MAX_ENTRIES_NO_TASK = 3;
6987
+ var MAX_ENTRIES_WITH_TASK = 5;
7009
6988
  function extractKeywords(text) {
7010
- return [...new Set(text.toLowerCase().split(/\s+/).filter((w) => w.length > 3))];
6989
+ return [...new Set(text.toLowerCase().split(/\s+/).filter((w) => w.length > 2))];
7011
6990
  }
7012
6991
  function scoreEntry(entry, keywords) {
7013
6992
  const text = `${entry.name} ${entry.description ?? ""}`.toLowerCase();
@@ -7020,16 +6999,18 @@ function formatGovernanceEntry(entry) {
7020
6999
  }
7021
7000
  function buildOperatingProtocol(governanceOrStandards, task) {
7022
7001
  const governance = Array.isArray(governanceOrStandards) ? { standards: governanceOrStandards } : governanceOrStandards ?? {};
7002
+ const { principles = [], standards = [], businessRules = [] } = governance;
7003
+ const hasGovernance = principles.length > 0 || standards.length > 0 || businessRules.length > 0;
7004
+ const rule5 = hasGovernance ? RULE5_WITH_GOVERNANCE : RULE5_COMPACT;
7005
+ const protocol = [...CORE_PROTOCOL_BASE, rule5];
7023
7006
  const lines = [
7024
7007
  "## Operating Protocol",
7025
7008
  "_How to work in this workspace. Follow these before and during every task._",
7026
7009
  ""
7027
7010
  ];
7028
- for (let i = 0; i < CORE_PROTOCOL.length; i++) {
7029
- lines.push(`${i + 1}. ${CORE_PROTOCOL[i]}`);
7011
+ for (let i = 0; i < protocol.length; i++) {
7012
+ lines.push(`${i + 1}. ${protocol[i]}`);
7030
7013
  }
7031
- const { principles = [], standards = [], businessRules = [] } = governance;
7032
- const hasGovernance = principles.length > 0 || standards.length > 0 || businessRules.length > 0;
7033
7014
  if (hasGovernance) {
7034
7015
  lines.push("");
7035
7016
  lines.push("### Workspace governance directives");
@@ -7041,13 +7022,20 @@ function buildOperatingProtocol(governanceOrStandards, task) {
7041
7022
  if (task) {
7042
7023
  const keywords = extractKeywords(task);
7043
7024
  let anyRelevant = false;
7025
+ let shownCount = 0;
7026
+ let totalRelevant = 0;
7027
+ let totalGovernance = 0;
7044
7028
  for (const type of types) {
7045
7029
  if (type.entries.length === 0) continue;
7030
+ totalGovernance += type.entries.length;
7046
7031
  const scored = type.entries.map((e) => ({ entry: e, score: scoreEntry(e, keywords) })).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
7047
7032
  if (scored.length > 0) {
7048
7033
  anyRelevant = true;
7034
+ totalRelevant += scored.length;
7035
+ const capped = scored.slice(0, MAX_ENTRIES_WITH_TASK);
7036
+ shownCount += capped.length;
7049
7037
  lines.push(type.header);
7050
- for (const s of scored) {
7038
+ for (const s of capped) {
7051
7039
  lines.push(formatGovernanceEntry(s.entry));
7052
7040
  }
7053
7041
  lines.push("");
@@ -7062,10 +7050,15 @@ function buildOperatingProtocol(governanceOrStandards, task) {
7062
7050
  }
7063
7051
  lines.push("");
7064
7052
  }
7053
+ lines.push(
7054
+ `_No governance matched task. Showing top ${MAX_ENTRIES_NO_TASK} per type. Run \`orient\` without a task to see all._`
7055
+ );
7056
+ } else {
7057
+ const overflow = totalRelevant > shownCount ? ` ${totalRelevant - shownCount} more matched \u2014 use \`entries action=search query="..."\` before proposing.` : "";
7058
+ lines.push(
7059
+ `_${shownCount} of ${totalGovernance} governance entries shown, filtered by task.${overflow}_`
7060
+ );
7065
7061
  }
7066
- lines.push(
7067
- "_Governance filtered by task relevance. Run `orient` without a task to see all._"
7068
- );
7069
7062
  } else {
7070
7063
  for (const type of types) {
7071
7064
  if (type.entries.length === 0) continue;
@@ -7155,12 +7148,12 @@ function getInterviewInstructions(workspaceName) {
7155
7148
  return {
7156
7149
  systemPrompt: `You are activating the **${workspaceName}** Product Brain. Ask 1\u20132 focused questions, then extract structured knowledge and feed it to batch-capture. In Open governance mode, entries are committed automatically. In consensus/role mode, they stay as drafts for review.`,
7157
7150
  question1: `**Q1 \u2014 What are you building and for whom?** Describe your product in 1\u20132 sentences and who it's for. (This becomes your Product Vision and primary Audience.)`,
7158
- question2: `**Q2 (optional) \u2014 What's your tech stack or key domain terms?** Name a few core technologies or terms your team uses. (These become Architecture + Glossary entries.)`,
7151
+ question2: `**Q2 (optional)** What's one word or phrase that would trip someone up if they didn't know your context? (This becomes your first glossary term \u2014 the one that matters most.)`,
7159
7152
  extractionGuidance: `After the user answers, extract:
7160
7153
  - vision: the core product purpose (required)
7161
7154
  - audience: who it's for (optional)
7162
7155
  - techStack: technologies mentioned (optional, array)
7163
- - keyTerms: domain terms (optional, array)
7156
+ - keyTerms: domain terms \u2014 Q2 answer (the word/phrase that trips people up) goes here (optional, array)
7164
7157
  - keyDecisions: any decisions stated (optional, array)
7165
7158
  - tensions: any pain points stated (optional, array)
7166
7159
 
@@ -7174,19 +7167,22 @@ Map to batch-capture entries:
7174
7167
  captureInstructions: `Call batch-capture with the extracted entries. Omit \`autoCommit\` to follow workspace governance automatically, or pass \`autoCommit: false\` if the user wants a review-first pass. Keep descriptions concise (1\u20132 sentences each). Prefer 8 good entries over 15 mediocre ones \u2014 quality over volume. If batch-capture returns failedEntries, tell the user and retry those individually.`,
7175
7168
  qualityNote: (
7176
7169
  // FEAT-149: Retrieval-First Proof Moment.
7177
- // After capture, demonstrate retrieval instead of offering commit ceremony.
7178
- // The aha moment is the AI using the user's knowledge, not the user entering it.
7170
+ // The aha is friction elimination ("I'll never have to explain this again"),
7171
+ // not recognition ("it remembered me"). Simulate a FUTURE session, not a recap.
7179
7172
  `After capturing, silently call graph action=suggest on the strategy or architecture entry to build connections.
7180
7173
 
7181
7174
  **Then demonstrate the proof moment \u2014 this is the most important step:**
7182
7175
  Based on what the user told you, invent a specific, plausible development task they might actually do next (e.g. "add user authentication" for a SaaS app, "build the recommendation engine" for a marketplace, "set up the API layer" for a developer tool). Call \`context action=gather task="<your invented task>"\` to load their captured knowledge.
7183
7176
 
7184
- Present the results conversationally:
7185
- "Here's what I already know about your product that I didn't know 5 minutes ago \u2014 if you asked me to **<task>**, I'd bring this context:"
7177
+ Present with the "tomorrow" framing \u2014 simulate a future session, not a recap:
7178
+ "Okay \u2014 let me show you what just changed. Tomorrow, if you open a new tab and ask me to help with **<task>**, I'd already know:
7179
+ - [vision applied to that task \u2014 one sentence]
7180
+ - [ICP applied \u2014 who this feature is for in their context]
7181
+ - [any key term that matters for this task]
7186
7182
 
7187
- Then list the key entries that context returned: their product vision, audience, tech decisions, glossary terms. Keep it concrete \u2014 entry names and one-line summaries, not abstract descriptions.
7183
+ That's context you won't have to explain again."
7188
7184
 
7189
- End with: "**Try it \u2014 ask me to help with something on your product.** I'll use everything I just learned."`
7185
+ End with: "**Try it right now** \u2014 ask me something you'd normally have to re-explain first. I'll answer like I've known your product for months."`
7190
7186
  ),
7191
7187
  scanOffer: `After the proof moment, offer the codebase scan as a way to deepen the knowledge:
7192
7188
  "Want me to learn even more? I can scan your project files (README, package.json, source structure) and pick up technical decisions, conventions, and architecture that I missed. Takes about 2 minutes."`
@@ -7221,6 +7217,9 @@ async function tryMarkOriented(agentSessionId) {
7221
7217
  var startSchema = z15.object({
7222
7218
  preset: z15.string().optional().describe(
7223
7219
  "Collection preset ID to seed (e.g. 'software-product', 'content-business', 'agency', 'saas-api', 'general'). Only used for fresh workspaces. If omitted on a fresh workspace, returns the preset menu."
7220
+ ),
7221
+ task: z15.string().optional().describe(
7222
+ "What you're about to work on (e.g. 'implementing auth middleware', 'refactoring the API layer'). Filters governance to show only relevant principles, standards, and business rules."
7224
7223
  )
7225
7224
  });
7226
7225
  function registerStartTools(server) {
@@ -7232,7 +7231,7 @@ function registerStartTools(server) {
7232
7231
  inputSchema: startSchema,
7233
7232
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
7234
7233
  },
7235
- withEnvelope(async ({ preset }) => {
7234
+ withEnvelope(async ({ preset, task }) => {
7236
7235
  const errors = [];
7237
7236
  const agentSessionId = getAgentSessionId();
7238
7237
  let wsCtx = null;
@@ -7292,7 +7291,7 @@ function registerStartTools(server) {
7292
7291
  void mcpMutation("chain.setOnboardingCompleted", {}).catch(() => {
7293
7292
  });
7294
7293
  }
7295
- const orientResult = await buildOrientResponse(wsCtx, agentSessionId, errors);
7294
+ const orientResult = await buildOrientResponse(wsCtx, agentSessionId, errors, task);
7296
7295
  return {
7297
7296
  content: [{ type: "text", text: orientResult.text }],
7298
7297
  structuredContent: orientResult.structuredContent
@@ -7302,73 +7301,83 @@ function registerStartTools(server) {
7302
7301
  }
7303
7302
  function buildBlankResponse(wsCtx, sessionCtx) {
7304
7303
  const instructions = getInterviewInstructions(wsCtx.workspaceName);
7304
+ const presetList = listPresets();
7305
+ const presets = presetList.map((p) => p.id).join(", ");
7305
7306
  const text = [
7307
+ // ── User-facing welcome — present this to the user ─────────────────
7306
7308
  `# Welcome to ${wsCtx.workspaceName}`,
7307
7309
  "",
7308
- "Your workspace is fresh. Let's set it up.",
7310
+ "Your workspace is fresh. Let's bring it to life.",
7309
7311
  "",
7310
- "## How would you like to start?",
7312
+ "Tell me what you're building \u2014 paste a doc, describe it in a sentence, or say **scan my codebase**.",
7311
7313
  "",
7312
- "Present these options to the user:",
7314
+ "---",
7313
7315
  "",
7314
- "**1. Tell me about your product** _(recommended)_",
7315
- "Ask the user:",
7316
- `> ${instructions.question1}`,
7316
+ // ── Agent instructions follow these exactly, do not present them ──
7317
+ "## Agent Instructions",
7317
7318
  "",
7318
- "_A sentence or two is enough. This becomes the foundation \u2014 vision and audience \u2014 that makes everything else more relevant._",
7319
+ "### Step 1 \u2014 Open with their name (FLO-1)",
7320
+ `Ask: _"What should I call you?"_ before asking about the product. Use their name throughout.`,
7319
7321
  "",
7320
- "**2. Scan my codebase**",
7321
- "Skip the questions and jump straight to reading the project files.",
7322
+ "### Step 2 \u2014 Detect path from their first response (no menu, no numbered options)",
7322
7323
  "",
7323
- "**3. Use a preset**",
7324
- `Seed collections for a domain. Available: software-product, content-business, agency, saas-api, general.`,
7324
+ "Read signals from the user's first substantive message and branch silently:",
7325
7325
  "",
7326
- "---",
7326
+ "**document** \u2014 paste >200 words, markdown headers, or says 'here's my doc/brief/spec'",
7327
+ "\u2192 Extract vision, audience, keyTerms, keyDecisions, tensions from the document.",
7328
+ "\u2192 Present a numbered confirmation list before capturing. Capture only confirmed entries.",
7329
+ "\u2192 Fallback: if sparse or unclear, switch to interview path.",
7327
7330
  "",
7328
- "## Path 1: Interview first (default if user just starts talking)",
7331
+ "**codebase** \u2014 mentions 'scan', 'codebase', README, or provides a file path",
7332
+ `\u2192 First ask: _"Tell me one thing about this product that's not obvious from the code \u2014 what's it actually for?"_`,
7333
+ "\u2192 Then read: README.md, package.json / pyproject.toml / Cargo.toml, top-level source dirs.",
7334
+ "\u2192 Infer 5\u20138 entries. Present numbered list for confirmation. Capture only confirmed entries.",
7335
+ "\u2192 Fallback: if no README or empty project, immediately switch to interview path.",
7329
7336
  "",
7330
- instructions.systemPrompt,
7337
+ `**preset** \u2014 mentions a domain keyword (SaaS, agency, content business, API)`,
7338
+ `\u2192 Confirm which preset fits. Available: ${presets}.`,
7339
+ "\u2192 Call `start` with `preset: '<chosen-preset>'`.",
7340
+ "\u2192 Fallback: if no preset matches or seeding fails, switch to interview path.",
7331
7341
  "",
7332
- "After the user answers Q1, extract:",
7342
+ "**interview** (default \u2014 1\u20135 sentence description or anything else)",
7343
+ `\u2192 ${instructions.systemPrompt}`,
7344
+ `\u2192 Q1: ${instructions.question1}`,
7345
+ `\u2192 Q2 (optional): _"What's one word or phrase that would trip someone up if they didn't know your context?"_ (This becomes their first glossary term.)`,
7333
7346
  instructions.extractionGuidance,
7347
+ `\u2192 ${instructions.captureInstructions}`,
7348
+ "\u2192 Omit `autoCommit` \u2014 workspace governance applies automatically.",
7334
7349
  "",
7335
- instructions.captureInstructions,
7336
- "Omit `autoCommit` \u2014 capture tools auto-commit in Open mode and create drafts in consensus/role mode.",
7350
+ "### Step 3 \u2014 Proof moment (most important \u2014 run after every path)",
7337
7351
  "",
7338
7352
  instructions.qualityNote,
7339
7353
  "",
7340
- instructions.scanOffer,
7354
+ "### Step 4 \u2014 Deepen (offer after proof moment)",
7341
7355
  "",
7356
+ instructions.scanOffer,
7342
7357
  "If they say yes, scan README.md, package.json/pyproject.toml/Cargo.toml, and top-level source dirs.",
7343
- "Infer entries WITH the product context you now have. Present as a numbered list for confirmation.",
7344
- "Do not capture anything until the user confirms what to keep. Then capture only the confirmed entries and let normal workspace governance apply.",
7345
- "",
7346
- "## Path 2: Scan first",
7347
- "",
7348
- "Read these files:",
7349
- "1. README.md (or equivalent top-level docs)",
7350
- "2. package.json / pyproject.toml / Cargo.toml (project manifest)",
7351
- "3. Top-level source directories and their purpose",
7352
- "",
7353
- "Infer 5\u20138 product knowledge entries (product name, tech decisions, features, conventions).",
7354
- "Present as a numbered list. Ask which to keep. Only after confirmation, capture the confirmed entries and let normal workspace governance apply.",
7355
- "",
7356
- "**If the codebase is sparse** (no README, empty project): fall back to Path 1 (ask the user about their product).",
7357
- "",
7358
- "_Prefer 5 specific entries over 8 generic ones. Skip anything that would apply to any codebase._",
7359
- "",
7360
- "**After scan capture, run the proof moment (same as Path 1):**",
7361
- instructions.qualityNote,
7362
- "",
7363
- "## Path 3: Preset",
7364
- "",
7365
- "If the user picks a preset, call `start` again with `preset: '<chosen-preset>'`."
7358
+ "Infer entries with the product context you now have. Present as a numbered list for confirmation.",
7359
+ "Capture only confirmed entries."
7366
7360
  ].join("\n");
7367
7361
  return {
7368
7362
  text,
7369
7363
  structuredContent: success(
7370
- `Workspace is blank. Choose: tell me about your product, scan codebase, or use a preset.`,
7371
- { stage: "blank", ...sessionCtx },
7364
+ "Workspace is blank. Ready for activation \u2014 interview, document, codebase scan, or preset.",
7365
+ {
7366
+ stage: "blank",
7367
+ interviewSchema: {
7368
+ fields: ["vision", "audience", "techStack", "keyTerms", "keyDecisions", "tensions"],
7369
+ mapping: {
7370
+ vision: "strategy",
7371
+ audience: "audiences",
7372
+ techStack: "architecture + top 3 terms \u2192 glossary",
7373
+ keyTerms: "glossary",
7374
+ keyDecisions: "decisions",
7375
+ tensions: "tensions"
7376
+ }
7377
+ },
7378
+ availablePresets: presetList.map((p) => ({ id: p.id, name: p.name })),
7379
+ ...sessionCtx
7380
+ },
7372
7381
  [
7373
7382
  { tool: "capture", description: "Capture product knowledge", parameters: {} },
7374
7383
  { tool: "start", description: "Seed with a preset", parameters: { preset: "software-product" } }
@@ -7524,7 +7533,7 @@ function pickNextAction(gaps, openTensions, priorSessions) {
7524
7533
  }
7525
7534
  return null;
7526
7535
  }
7527
- async function buildOrientResponse(wsCtx, agentSessionId, errors) {
7536
+ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
7528
7537
  const wsFullCtx = await getWorkspaceContext();
7529
7538
  const { ageDays, isNeglected } = computeWorkspaceAge(wsFullCtx.createdAt);
7530
7539
  let priorSessions = [];
@@ -7583,125 +7592,96 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
7583
7592
  lines.push("_Need a collection that doesn't exist yet (e.g. audiences, personas, metrics)? Just ask \u2014 I'll set it up._");
7584
7593
  lines.push("");
7585
7594
  } else if (isHighReadiness) {
7586
- let wsPrinciples = [];
7587
- let wsStandards = [];
7588
- let wsBusinessRules = [];
7595
+ let activeBets = [];
7589
7596
  try {
7590
- const govSlugs = ["principles", "standards", "business-rules"];
7591
- const govLabels = {
7592
- principles: "Principles",
7593
- standards: "Standards",
7594
- "business-rules": "Business Rules"
7595
- };
7596
- const govEntries = [];
7597
- for (const slug of govSlugs) {
7598
- const entries = await mcpQuery("chain.listEntries", { collectionSlug: slug });
7599
- const active = (entries ?? []).filter((e) => e.status === "active");
7600
- if (active.length > 0) {
7601
- govEntries.push({ slug, entries: active });
7597
+ const betEntries = await mcpQuery("chain.listEntries", { collectionSlug: "bets" });
7598
+ activeBets = (betEntries ?? []).filter((e) => e.status === "active" && e.data?.horizon === "now").slice(0, 8);
7599
+ } catch {
7600
+ }
7601
+ if (task) {
7602
+ if (activeBets.length > 0) {
7603
+ lines.push("");
7604
+ lines.push("## Active bets \u2014 current scope");
7605
+ lines.push("_Work outside these bets requires explicit user confirmation._");
7606
+ lines.push("");
7607
+ for (const e of activeBets) {
7608
+ lines.push(`- \`${e.entryId ?? e.name}\` ${e.name}`);
7609
+ }
7610
+ lines.push("");
7611
+ }
7612
+ let wsPrinciples = [];
7613
+ let wsStandards = [];
7614
+ let wsBusinessRules = [];
7615
+ try {
7616
+ for (const slug of ["principles", "standards", "business-rules"]) {
7617
+ const entries = await mcpQuery("chain.listEntries", { collectionSlug: slug });
7618
+ const active = (entries ?? []).filter((e) => e.status === "active");
7602
7619
  if (slug === "principles") wsPrinciples = active;
7603
7620
  if (slug === "standards") wsStandards = active;
7604
7621
  if (slug === "business-rules") wsBusinessRules = active;
7605
7622
  }
7623
+ } catch {
7606
7624
  }
7607
- if (govEntries.length > 0) {
7625
+ const mapGovEntry = (e) => ({
7626
+ entryId: e.entryId,
7627
+ name: e.name,
7628
+ description: typeof e.data?.description === "string" ? e.data.description : typeof e.description === "string" ? e.description : void 0
7629
+ });
7630
+ lines.push(...buildOperatingProtocol({
7631
+ principles: wsPrinciples.map(mapGovEntry),
7632
+ standards: wsStandards.map(mapGovEntry),
7633
+ businessRules: wsBusinessRules.map(mapGovEntry)
7634
+ }, task));
7635
+ const plannedWork = await queryPlannedWork();
7636
+ if (hasPlannedWork(plannedWork)) {
7608
7637
  lines.push("");
7609
- lines.push("## Workspace Governance \u2014 constraints");
7610
- lines.push("_These constrain what you build. If your proposal conflicts with any of these, stop and flag it._");
7638
+ lines.push(...buildPlannedWorkSection(plannedWork, priorSessions, recoveryBlock));
7639
+ } else if (recoveryBlock) {
7611
7640
  lines.push("");
7612
- for (const g of govEntries) {
7613
- lines.push(`**${govLabels[g.slug]}:**`);
7614
- for (const e of g.entries.slice(0, 8)) {
7615
- lines.push(`- \`${e.entryId ?? e.name}\` ${e.name}`);
7616
- }
7641
+ lines.push(...formatRecoveryBlock(recoveryBlock));
7642
+ }
7643
+ try {
7644
+ const allEntries = await mcpQuery("chain.listEntries", {});
7645
+ const committed = (allEntries ?? []).filter(
7646
+ (e) => e.status === "active" && !e.seededByPlatform
7647
+ );
7648
+ const committedCollections = new Set(committed.map((e) => e.collectionId ?? e.collection));
7649
+ if (committed.length >= 10 && committedCollections.size >= 3) {
7617
7650
  lines.push("");
7651
+ lines.push(`_${committed.length} entries across ${committedCollections.size} collections on the Chain._`);
7618
7652
  }
7653
+ } catch {
7619
7654
  }
7620
- } catch {
7621
- }
7622
- let activeBets = [];
7623
- try {
7624
- const betEntries = await mcpQuery("chain.listEntries", { collectionSlug: "bets" });
7625
- activeBets = (betEntries ?? []).filter((e) => e.status === "active" && e.data?.horizon === "now").slice(0, 8);
7655
+ lines.push("");
7656
+ lines.push(`Working on: **${task}**`);
7657
+ lines.push("");
7658
+ } else {
7626
7659
  if (activeBets.length > 0) {
7627
7660
  lines.push("");
7628
7661
  lines.push("## Active bets \u2014 current scope");
7629
- lines.push("_These define what you're building now. Work outside these bets requires explicit user confirmation before designing._");
7662
+ lines.push("_Work outside these bets requires explicit user confirmation._");
7630
7663
  lines.push("");
7631
7664
  for (const e of activeBets) {
7632
7665
  lines.push(`- \`${e.entryId ?? e.name}\` ${e.name}`);
7633
7666
  }
7634
7667
  lines.push("");
7635
7668
  }
7636
- } catch {
7637
- }
7638
- const mapGovEntry = (e) => ({
7639
- entryId: e.entryId,
7640
- name: e.name,
7641
- description: typeof e.data?.description === "string" ? e.data.description : typeof e.description === "string" ? e.description : void 0
7642
- });
7643
- lines.push(...buildOperatingProtocol({
7644
- principles: wsPrinciples.map(mapGovEntry),
7645
- standards: wsStandards.map(mapGovEntry),
7646
- businessRules: wsBusinessRules.map(mapGovEntry)
7647
- }));
7648
- const plannedWork = await queryPlannedWork();
7649
- if (hasPlannedWork(plannedWork)) {
7650
- lines.push("");
7651
- lines.push(...buildPlannedWorkSection(plannedWork, priorSessions, recoveryBlock));
7652
- } else {
7653
- const briefingItems = [];
7669
+ lines.push(...buildOperatingProtocol());
7654
7670
  if (priorSessions.length > 0 && !recoveryBlock) {
7655
7671
  const last = priorSessions[0];
7656
- const date = new Date(last.startedAt).toISOString().split("T")[0];
7672
+ const date = last.startedAt ? new Date(last.startedAt).toISOString().split("T")[0] : "unknown";
7657
7673
  const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
7658
7674
  const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
7659
- briefingItems.push(`**Last session** (${date}): ${created} created, ${modified} modified`);
7660
- }
7661
- if (readiness?.gaps?.length > 0) {
7662
- briefingItems.push(`**${readiness.gaps.length} gap${readiness.gaps.length === 1 ? "" : "s"}** remaining`);
7663
- }
7664
- if (briefingItems.length > 0) {
7665
- lines.push("");
7666
- lines.push("## Briefing");
7667
- for (const item of briefingItems) {
7668
- lines.push(`- ${item}`);
7669
- }
7675
+ lines.push(`_Last session (${date}): ${created} created, ${modified} modified._`);
7670
7676
  lines.push("");
7671
7677
  }
7672
7678
  if (recoveryBlock) {
7673
7679
  lines.push("");
7674
7680
  lines.push(...formatRecoveryBlock(recoveryBlock));
7675
7681
  }
7682
+ lines.push("**What are you working on?** Tell me and I'll load relevant governance and context.");
7683
+ lines.push("");
7676
7684
  }
7677
- try {
7678
- const allEntries = await mcpQuery("chain.listEntries", {});
7679
- const committed = (allEntries ?? []).filter(
7680
- (e) => e.status === "active" && !e.seededByPlatform
7681
- );
7682
- const committedCollections = new Set(committed.map((e) => e.collectionId ?? e.collection));
7683
- if (committed.length >= 10 && committedCollections.size >= 3) {
7684
- lines.push("");
7685
- lines.push("## Your workspace is activated");
7686
- lines.push(
7687
- `**${committed.length} entries** across **${committedCollections.size} collections** \u2014 your knowledge graph is alive.`
7688
- );
7689
- lines.push("");
7690
- lines.push(
7691
- `**Try it:** Ask me anything about your product \u2014 architecture, decisions, tensions \u2014 and I'll pull context from the graph.`
7692
- );
7693
- lines.push("");
7694
- if (wsCtx.workspaceSlug) {
7695
- lines.push(`**View in Studio:** \`/w/${wsCtx.workspaceSlug}/studio\``);
7696
- lines.push("");
7697
- }
7698
- lines.push("_Tip: Ask me to suggest connections between entries to unlock deeper context._");
7699
- lines.push("");
7700
- }
7701
- } catch {
7702
- }
7703
- lines.push("What would you like to work on?");
7704
- lines.push("");
7705
7685
  }
7706
7686
  if (errors.length > 0) {
7707
7687
  lines.push("## Errors");
@@ -10060,23 +10040,27 @@ function registerHealthTools(server) {
10060
10040
  lines.push(...formatRecoveryBlock(recoveryBlock));
10061
10041
  } else if (priorSessions.length > 0) {
10062
10042
  const last = priorSessions[0];
10063
- const date = new Date(last.startedAt).toISOString().split("T")[0];
10043
+ const date = last.startedAt ? new Date(last.startedAt).toISOString().split("T")[0] : "unknown";
10064
10044
  const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
10065
10045
  const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
10066
10046
  lines.push(`Last session (${date}): ${created} created, ${modified} modified`);
10067
10047
  }
10068
10048
  if (orientEntries) {
10069
- const mapGovernanceEntry = (e) => ({
10070
- entryId: e.entryId,
10071
- name: e.name,
10072
- description: typeof e.preview === "string" ? e.preview : void 0
10073
- });
10074
10049
  lines.push("");
10075
- lines.push(...buildOperatingProtocol({
10076
- principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
10077
- standards: (orientEntries.standards ?? []).map(mapGovernanceEntry),
10078
- businessRules: (orientEntries.businessRules ?? []).map(mapGovernanceEntry)
10079
- }, task));
10050
+ if (task) {
10051
+ const mapGovernanceEntry = (e) => ({
10052
+ entryId: e.entryId,
10053
+ name: e.name,
10054
+ description: typeof e.preview === "string" ? e.preview : void 0
10055
+ });
10056
+ lines.push(...buildOperatingProtocol({
10057
+ principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
10058
+ standards: (orientEntries.standards ?? []).map(mapGovernanceEntry),
10059
+ businessRules: (orientEntries.businessRules ?? []).map(mapGovernanceEntry)
10060
+ }, task));
10061
+ } else {
10062
+ lines.push(...buildOperatingProtocol());
10063
+ }
10080
10064
  }
10081
10065
  if (agentSessionId) {
10082
10066
  try {
@@ -10136,218 +10120,164 @@ function registerHealthTools(server) {
10136
10120
  lines.push("_Use `collections action=create` to add it, or ask me to propose collections for your domain._");
10137
10121
  lines.push("");
10138
10122
  } else if (readiness) {
10139
- lines.push(`**Brain stage: ${orientStage}.**`);
10140
- lines.push("");
10141
- if (orientEntries?.strategicContext) {
10142
- const sc = orientEntries.strategicContext;
10143
- lines.push("## Strategic Context");
10144
- if (sc.vision) lines.push(`**Vision:** ${sc.vision}`);
10145
- if (sc.purpose) lines.push(`**Purpose:** ${sc.purpose}`);
10146
- if (sc.productAreaCount != null && sc.productAreaCount > 0) {
10147
- lines.push(`**Product areas (${sc.productAreaCount}):** ${(sc.productAreas ?? []).join(", ")}`);
10148
- }
10149
- const betLine = sc.currentBet ? `**Current bet:** ${sc.currentBet}. ${sc.activeBetCount} active bet(s).` : "No active bets.";
10150
- lines.push(`${betLine} ${sc.activeTensionCount} open tension(s).`);
10151
- lines.push("");
10152
- }
10153
- if (orientEntries?.continuingFrom && orientEntries.continuingFrom.length > 0) {
10154
- lines.push("## Continuing from");
10155
- lines.push("_Prior-session entries most relevant to your task._");
10156
- lines.push("");
10157
- for (const e of orientEntries.continuingFrom) {
10158
- const id = e.entryId ?? e.name;
10159
- const type = e.canonicalKey ?? "generic";
10160
- const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10161
- lines.push(`- \`${id}\` (score ${e.score}) [${type}]${coll} \u2014 ${e.name}`);
10162
- if (e.reasoning) lines.push(` _${e.reasoning}_`);
10163
- }
10164
- lines.push("");
10165
- }
10166
- if (orientEntries?.lastSessionTouched && orientEntries.lastSessionTouched.length > 0) {
10167
- lines.push("## Last session touched");
10168
- lines.push("_Entries created or modified in your most recent session._");
10169
- lines.push("");
10170
- for (const e of orientEntries.lastSessionTouched) {
10171
- const id = e.entryId ?? e.name;
10172
- const type = e.canonicalKey ?? "generic";
10173
- const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10174
- lines.push(`- \`${id}\` [${type}]${coll} \u2014 ${e.name}`);
10175
- }
10176
- lines.push("");
10177
- }
10178
- if (orientEntries?.taskContext && orientEntries.taskContext.context.length > 0) {
10179
- const tc = orientEntries.taskContext;
10180
- lines.push("## Task Context");
10181
- lines.push(`_Task-scoped entries (${tc.confidence} confidence, ${tc.totalFound} relevant)`);
10123
+ const fmt = (e) => {
10124
+ const type = e.canonicalKey ?? "generic";
10125
+ const stratum = e.stratum ?? "?";
10126
+ return `- \`${e.entryId ?? e._id}\` [${type} \xB7 ${stratum}] ${e.name}`;
10127
+ };
10128
+ if (task) {
10129
+ lines.push(`**Brain stage: ${orientStage}.** Working on: **${task}**`);
10182
10130
  lines.push("");
10183
- for (const e of tc.context) {
10184
- const id = e.entryId ?? e.name;
10185
- const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10186
- lines.push(`- \`${id}\` (score ${e.score})${coll}${e.name !== id ? ` \u2014 ${e.name}` : ""}`);
10131
+ if (orientEntries?.strategicContext) {
10132
+ const sc = orientEntries.strategicContext;
10133
+ lines.push("## Strategic Context");
10134
+ if (sc.vision) lines.push(`**Vision:** ${sc.vision}`);
10135
+ if (sc.purpose) lines.push(`**Purpose:** ${sc.purpose}`);
10136
+ if (sc.productAreaCount != null && sc.productAreaCount > 0) {
10137
+ lines.push(`**Product areas (${sc.productAreaCount}):** ${(sc.productAreas ?? []).join(", ")}`);
10138
+ }
10139
+ const betLine = sc.currentBet ? `**Current bet:** ${sc.currentBet}. ${sc.activeBetCount} active bet(s).` : "No active bets.";
10140
+ lines.push(`${betLine} ${sc.activeTensionCount} open tension(s).`);
10141
+ lines.push("");
10187
10142
  }
10188
- lines.push("");
10189
- }
10190
- if (task && orientEntries) {
10191
- const result = runAlignmentCheck(
10192
- task,
10193
- orientEntries.activeBets ?? [],
10194
- orientEntries.taskContext?.context
10195
- );
10196
- lines.push(...buildAlignmentCheckLines(result));
10197
- }
10198
- if (orientEntries) {
10199
- const fmt = (e) => {
10200
- const type = e.canonicalKey ?? "generic";
10201
- const stratum = e.stratum ?? "?";
10202
- return `- \`${e.entryId ?? e._id}\` [${type} \xB7 ${stratum}] ${e.name}`;
10203
- };
10204
- if (orientEntries.activeBets?.length > 0) {
10205
- lines.push("## Active bets \u2014 current scope");
10206
- lines.push("_These define what you're building now. Work outside these bets requires explicit user confirmation before designing._");
10143
+ if (orientEntries?.continuingFrom && orientEntries.continuingFrom.length > 0) {
10144
+ lines.push("## Continuing from");
10145
+ lines.push("_Prior-session entries most relevant to your task._");
10207
10146
  lines.push("");
10208
- for (const e of orientEntries.activeBets) {
10209
- lines.push(fmt(e));
10210
- const tensions = e.linkedTensions;
10211
- if (tensions?.length) {
10212
- const tensionLines = tensions.map((t) => {
10213
- const meta = [t.severity, t.priority].filter(Boolean).join(", ");
10214
- return `\`${t.entryId ?? t.name}\` (${t.name}${meta ? `, ${meta}` : ""})`;
10215
- });
10216
- lines.push(` Tensions: ${tensionLines.join("; ")}`);
10217
- } else {
10218
- lines.push(` Tensions: No linked tensions`);
10219
- }
10147
+ for (const e of orientEntries.continuingFrom) {
10148
+ const id = e.entryId ?? e.name;
10149
+ const type = e.canonicalKey ?? "generic";
10150
+ const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10151
+ lines.push(`- \`${id}\` (score ${e.score}) [${type}]${coll} \u2014 ${e.name}`);
10152
+ if (e.reasoning) lines.push(` _${e.reasoning}_`);
10220
10153
  }
10221
10154
  lines.push("");
10222
10155
  }
10223
- if (orientEntries.activeGoals.length > 0) {
10224
- lines.push("## Active goals");
10225
- orientEntries.activeGoals.forEach((e) => lines.push(fmt(e)));
10156
+ if (orientEntries?.lastSessionTouched && orientEntries.lastSessionTouched.length > 0) {
10157
+ lines.push("## Last session touched");
10158
+ lines.push("_Entries created or modified in your most recent session._");
10226
10159
  lines.push("");
10227
- }
10228
- if (orientEntries.recentDecisions.length > 0) {
10229
- lines.push("## Recent decisions");
10230
- orientEntries.recentDecisions.forEach((e) => lines.push(fmt(e)));
10160
+ for (const e of orientEntries.lastSessionTouched) {
10161
+ const id = e.entryId ?? e.name;
10162
+ const type = e.canonicalKey ?? "generic";
10163
+ const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10164
+ lines.push(`- \`${id}\` [${type}]${coll} \u2014 ${e.name}`);
10165
+ }
10231
10166
  lines.push("");
10232
10167
  }
10233
- if (orientEntries.recentlySuperseded.length > 0) {
10234
- lines.push("## Recently superseded");
10235
- orientEntries.recentlySuperseded.forEach((e) => lines.push(fmt(e)));
10168
+ if (orientEntries?.taskContext && orientEntries.taskContext.context.length > 0) {
10169
+ const tc = orientEntries.taskContext;
10170
+ lines.push("## Task Context");
10171
+ lines.push(`_Task-scoped entries (${tc.confidence} confidence, ${tc.totalFound} relevant)`);
10236
10172
  lines.push("");
10237
- }
10238
- if (orientEntries.staleEntries.length > 0) {
10239
- lines.push("## Needs confirmation");
10240
- lines.push(`_Domain stratum entries not confirmed in ${orientEntries.stalenessThresholdDays} days._`);
10241
- orientEntries.staleEntries.forEach((e) => lines.push(fmt(e)));
10173
+ for (const e of tc.context) {
10174
+ const id = e.entryId ?? e.name;
10175
+ const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
10176
+ lines.push(`- \`${id}\` (score ${e.score})${coll}${e.name !== id ? ` \u2014 ${e.name}` : ""}`);
10177
+ }
10242
10178
  lines.push("");
10243
10179
  }
10244
- const hasPrinciples = orientEntries.principles?.length > 0;
10245
- const hasStandards = orientEntries.standards?.length > 0;
10246
- const hasBusinessRules = orientEntries.businessRules.length > 0;
10247
- if (hasPrinciples || hasStandards || hasBusinessRules) {
10248
- lines.push("## Workspace Governance \u2014 constraints");
10249
- lines.push("_These constrain what you build. If your proposal conflicts with any of these, stop and flag it. Do not design around them without explicit user confirmation._");
10250
- lines.push("");
10251
- if (hasPrinciples) {
10252
- lines.push("**Principles** \u2014 beliefs that guide decisions:");
10253
- orientEntries.principles.forEach((e) => lines.push(fmt(e)));
10180
+ if (orientEntries) {
10181
+ const result = runAlignmentCheck(
10182
+ task,
10183
+ orientEntries.activeBets ?? [],
10184
+ orientEntries.taskContext?.context
10185
+ );
10186
+ lines.push(...buildAlignmentCheckLines(result));
10187
+ }
10188
+ if (orientEntries) {
10189
+ if (orientEntries.activeBets?.length > 0) {
10190
+ lines.push("## Active bets \u2014 current scope");
10191
+ lines.push("_Work outside these bets requires explicit user confirmation._");
10192
+ lines.push("");
10193
+ for (const e of orientEntries.activeBets) {
10194
+ lines.push(fmt(e));
10195
+ const tensions = e.linkedTensions;
10196
+ if (tensions?.length) {
10197
+ const tensionLines = tensions.map((t) => {
10198
+ const meta = [t.severity, t.priority].filter(Boolean).join(", ");
10199
+ return `\`${t.entryId ?? t.name}\` (${t.name}${meta ? `, ${meta}` : ""})`;
10200
+ });
10201
+ lines.push(` Tensions: ${tensionLines.join("; ")}`);
10202
+ }
10203
+ }
10204
+ lines.push("");
10205
+ }
10206
+ if (orientEntries.activeGoals?.length > 0) {
10207
+ lines.push("## Active goals");
10208
+ orientEntries.activeGoals.forEach((e) => lines.push(fmt(e)));
10209
+ lines.push("");
10210
+ }
10211
+ if (orientEntries.recentDecisions?.length > 0) {
10212
+ lines.push("## Recent decisions");
10213
+ orientEntries.recentDecisions.forEach((e) => lines.push(fmt(e)));
10254
10214
  lines.push("");
10255
10215
  }
10256
- if (hasStandards) {
10257
- lines.push("**Standards** \u2014 conventions for how work is done:");
10258
- orientEntries.standards.forEach((e) => lines.push(fmt(e)));
10216
+ if (orientEntries.recentlySuperseded?.length > 0) {
10217
+ lines.push("## Recently superseded");
10218
+ orientEntries.recentlySuperseded.forEach((e) => lines.push(fmt(e)));
10259
10219
  lines.push("");
10260
10220
  }
10261
- if (hasBusinessRules) {
10262
- lines.push("**Business Rules** \u2014 system constraints:");
10263
- orientEntries.businessRules.forEach((e) => lines.push(fmt(e)));
10221
+ if (orientEntries.staleEntries?.length > 0) {
10222
+ lines.push("## Needs confirmation");
10223
+ lines.push(`_Domain stratum entries not confirmed in ${orientEntries.stalenessThresholdDays} days._`);
10224
+ orientEntries.staleEntries.forEach((e) => lines.push(fmt(e)));
10264
10225
  lines.push("");
10265
10226
  }
10227
+ if (orientEntries.architectureNotes?.length > 0) {
10228
+ lines.push("## Architecture notes");
10229
+ orientEntries.architectureNotes.forEach((e) => lines.push(fmt(e)));
10230
+ lines.push("");
10231
+ }
10232
+ const mapGovernanceEntry = (e) => ({
10233
+ entryId: e.entryId,
10234
+ name: e.name,
10235
+ description: typeof e.preview === "string" ? e.preview : void 0
10236
+ });
10237
+ lines.push(...buildOperatingProtocol({
10238
+ principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
10239
+ standards: (orientEntries.standards ?? []).map(mapGovernanceEntry),
10240
+ businessRules: (orientEntries.businessRules ?? []).map(mapGovernanceEntry)
10241
+ }, task));
10266
10242
  }
10267
- if (orientEntries.architectureNotes.length > 0) {
10268
- lines.push("## Architecture notes");
10269
- orientEntries.architectureNotes.forEach((e) => lines.push(fmt(e)));
10270
- lines.push("");
10243
+ let allEntries = [];
10244
+ try {
10245
+ allEntries = await mcpQuery("chain.listEntries", {}) ?? [];
10246
+ } catch {
10247
+ }
10248
+ const plannedWork = buildPlannedWork(allEntries);
10249
+ if (hasPlannedWork(plannedWork)) {
10250
+ lines.push(...buildPlannedWorkSection(plannedWork, priorSessions, recoveryBlock));
10251
+ } else if (recoveryBlock) {
10252
+ lines.push(...formatRecoveryBlock(recoveryBlock));
10271
10253
  }
10272
- const mapGovernanceEntry = (e) => ({
10273
- entryId: e.entryId,
10274
- name: e.name,
10275
- description: typeof e.preview === "string" ? e.preview : void 0
10276
- });
10277
- lines.push(...buildOperatingProtocol({
10278
- principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
10279
- standards: (orientEntries.standards ?? []).map(mapGovernanceEntry),
10280
- businessRules: (orientEntries.businessRules ?? []).map(mapGovernanceEntry)
10281
- }, task));
10282
- }
10283
- let allEntries = [];
10284
- try {
10285
- allEntries = await mcpQuery("chain.listEntries", {}) ?? [];
10286
- } catch {
10287
- }
10288
- const plannedWork = buildPlannedWork(allEntries);
10289
- if (hasPlannedWork(plannedWork)) {
10290
- lines.push(...buildPlannedWorkSection(plannedWork, priorSessions, recoveryBlock));
10291
10254
  } else {
10292
- const briefingItems = [];
10255
+ lines.push(`**Brain stage: ${orientStage}.**`);
10256
+ lines.push("");
10257
+ if (orientEntries?.activeBets?.length) {
10258
+ lines.push("## Active bets \u2014 current scope");
10259
+ lines.push("_Work outside these bets requires explicit user confirmation._");
10260
+ lines.push("");
10261
+ for (const e of orientEntries.activeBets) {
10262
+ lines.push(fmt(e));
10263
+ }
10264
+ lines.push("");
10265
+ }
10266
+ lines.push(...buildOperatingProtocol());
10293
10267
  if (priorSessions.length > 0 && !recoveryBlock) {
10294
10268
  const last = priorSessions[0];
10295
- const date = new Date(last.startedAt).toISOString().split("T")[0];
10269
+ const date = last.startedAt ? new Date(last.startedAt).toISOString().split("T")[0] : "unknown";
10296
10270
  const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
10297
10271
  const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
10298
- briefingItems.push(`**Last session** (${date}): ${created} created, ${modified} modified`);
10299
- }
10300
- if (readiness.gaps?.length > 0) {
10301
- briefingItems.push(`**${readiness.gaps.length} gap${readiness.gaps.length === 1 ? "" : "s"}** remaining`);
10302
- }
10303
- if (briefingItems.length > 0) {
10304
- lines.push("## Briefing");
10305
- for (const item of briefingItems) {
10306
- lines.push(`- ${item}`);
10307
- }
10272
+ lines.push(`_Last session (${date}): ${created} created, ${modified} modified._`);
10308
10273
  lines.push("");
10309
10274
  }
10310
10275
  if (recoveryBlock) {
10311
- lines.push("");
10312
10276
  lines.push(...formatRecoveryBlock(recoveryBlock));
10313
10277
  }
10314
- }
10315
- const activeEntries = allEntries.filter((e) => e.status === "active");
10316
- if (activeEntries.length > 0) {
10317
- const orgHealth = computeOrganisationHealth(activeEntries);
10318
- if (orgHealth.disagreements > 0) {
10319
- lines.push("## Organisation Health");
10320
- lines.push(...formatOrgHealthLines(orgHealth, 3));
10321
- lines.push("");
10322
- }
10323
- }
10324
- const epistemicEntries = activeEntries.filter(
10325
- (e) => e.collectionSlug === "insights" || e.collectionSlug === "assumptions"
10326
- );
10327
- if (epistemicEntries.length > 0) {
10328
- let validated = 0;
10329
- let evidenced = 0;
10330
- let hypotheses = 0;
10331
- let untested = 0;
10332
- for (const e of epistemicEntries) {
10333
- const ws = e.workflowStatus;
10334
- if (ws === "validated") validated++;
10335
- else if (ws === "evidenced") evidenced++;
10336
- else if (ws === "testing") evidenced++;
10337
- else if (ws === "invalidated") validated++;
10338
- else if (e.collectionSlug === "assumptions") untested++;
10339
- else hypotheses++;
10340
- }
10341
- const parts = [];
10342
- if (validated > 0) parts.push(`${validated} validated`);
10343
- if (evidenced > 0) parts.push(`${evidenced} evidenced`);
10344
- if (hypotheses > 0) parts.push(`${hypotheses} hypotheses`);
10345
- if (untested > 0) parts.push(`${untested} untested`);
10346
- lines.push(`**Epistemic health:** ${parts.join(" \xB7 ")} _(${epistemicEntries.length} claim-carrying entries)_`);
10278
+ lines.push("**What are you working on?** Tell me and I'll load relevant governance and context.");
10347
10279
  lines.push("");
10348
10280
  }
10349
- lines.push("What would you like to work on?");
10350
- lines.push("");
10351
10281
  }
10352
10282
  if (errors.length > 0) {
10353
10283
  lines.push("## Errors");
@@ -11745,4 +11675,4 @@ export {
11745
11675
  SERVER_VERSION,
11746
11676
  createProductBrainServer
11747
11677
  };
11748
- //# sourceMappingURL=chunk-GKMXGRJW.js.map
11678
+ //# sourceMappingURL=chunk-4IQ7A5R4.js.map