@productbrain/mcp 0.0.1-beta.205 → 0.0.1-beta.207
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.
|
@@ -478,6 +478,13 @@ async function getWorkspaceContext() {
|
|
|
478
478
|
governanceMode: s.workspaceGovernanceMode ?? "open"
|
|
479
479
|
};
|
|
480
480
|
}
|
|
481
|
+
async function refreshWorkspaceGovernanceMode() {
|
|
482
|
+
const workspace = await kernelCall("resolveWorkspace", {});
|
|
483
|
+
const mode = workspace?.governanceMode ?? "open";
|
|
484
|
+
const s = state();
|
|
485
|
+
s.workspaceGovernanceMode = mode;
|
|
486
|
+
return mode;
|
|
487
|
+
}
|
|
481
488
|
async function kernelQuery(fn, args = {}) {
|
|
482
489
|
const workspaceId = await getWorkspaceId();
|
|
483
490
|
return kernelCall(fn, { ...args, workspaceId });
|
|
@@ -2425,19 +2432,19 @@ ${groundingReport.governance.map((g) => `- **${g.entryId}** ${g.name} [${g.colle
|
|
|
2425
2432
|
}
|
|
2426
2433
|
}
|
|
2427
2434
|
canonicalizeSelects(data, col.fields ?? [], isBetCapture);
|
|
2428
|
-
const status = "draft";
|
|
2429
2435
|
const agentId = getAgentSessionId();
|
|
2430
2436
|
let finalEntryId;
|
|
2431
2437
|
let internalId;
|
|
2432
2438
|
let normalizationMeta;
|
|
2433
2439
|
let formativeHints = [];
|
|
2434
2440
|
let relationSuggestionsFromCreate = [];
|
|
2441
|
+
let wasAutoCommittedServerSide = false;
|
|
2435
2442
|
try {
|
|
2436
2443
|
const result = await kernelMutation("chain.createEntry", {
|
|
2437
2444
|
collectionSlug: resolvedCollection,
|
|
2438
2445
|
entryId: entryId ?? void 0,
|
|
2439
2446
|
name,
|
|
2440
|
-
status,
|
|
2447
|
+
// DEC-896 / BR-76: omit status — server resolves from governanceMode (Open→active, else draft).
|
|
2441
2448
|
data,
|
|
2442
2449
|
canonicalKey,
|
|
2443
2450
|
createdBy: agentId ? `agent:${agentId}` : "capture",
|
|
@@ -2486,6 +2493,9 @@ No DB writes \u2014 call without \`preview:true\` to capture for real.${result.w
|
|
|
2486
2493
|
relationSuggestionsFromCreate = result.relationSuggestions ?? [];
|
|
2487
2494
|
formativeHints = result.qualityHints ?? [];
|
|
2488
2495
|
resolveGapsForEntry(name, result.entryId);
|
|
2496
|
+
if (result.status === "active") {
|
|
2497
|
+
wasAutoCommittedServerSide = true;
|
|
2498
|
+
}
|
|
2489
2499
|
} catch (error) {
|
|
2490
2500
|
const msg = error instanceof Error ? error.message : String(error);
|
|
2491
2501
|
if (msg.includes("Duplicate") || msg.includes("already exists")) {
|
|
@@ -2682,7 +2692,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2682
2692
|
} catch {
|
|
2683
2693
|
}
|
|
2684
2694
|
}
|
|
2685
|
-
const
|
|
2695
|
+
const freshGovernanceMode = await refreshWorkspaceGovernanceMode();
|
|
2696
|
+
const shouldAutoCommit = shouldAutoCommitCapture(autoCommit, freshGovernanceMode);
|
|
2686
2697
|
if (shouldAutoCommit && finalEntryId && conflictCandidates.length === 0) {
|
|
2687
2698
|
try {
|
|
2688
2699
|
const conflictResults = await kernelQuery("chain.searchEntries", { query: name });
|
|
@@ -2692,7 +2703,16 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2692
2703
|
}
|
|
2693
2704
|
let finalStatus = "draft";
|
|
2694
2705
|
let commitError = null;
|
|
2695
|
-
if (
|
|
2706
|
+
if (wasAutoCommittedServerSide) {
|
|
2707
|
+
finalStatus = "committed";
|
|
2708
|
+
await recordSessionActivity({ entryModified: internalId });
|
|
2709
|
+
trackChainEntryCommitted(wsCtx.workspaceId, {
|
|
2710
|
+
entry_id: finalEntryId,
|
|
2711
|
+
collection: resolvedCollection,
|
|
2712
|
+
commit_method: "auto",
|
|
2713
|
+
surface: "mcp_capture"
|
|
2714
|
+
});
|
|
2715
|
+
} else if (shouldAutoCommit && finalEntryId) {
|
|
2696
2716
|
try {
|
|
2697
2717
|
const semanticConflicts = await runSemanticConflictPreflight(name, description, resolvedCollection);
|
|
2698
2718
|
const blockingConflicts = semanticConflicts.filter(
|
|
@@ -2719,6 +2739,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2719
2739
|
}
|
|
2720
2740
|
} catch (e) {
|
|
2721
2741
|
commitError = e instanceof Error ? e.message : "unknown error";
|
|
2742
|
+
finalStatus = "draft_on_failure";
|
|
2743
|
+
await recordCommitFailure({ entryId: finalEntryId, error: e, sessionId: agentId, server });
|
|
2722
2744
|
}
|
|
2723
2745
|
}
|
|
2724
2746
|
const lines = [
|
|
@@ -2770,7 +2792,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
2770
2792
|
for (const r of userLinkResults) lines.push(`- ${r.label}`);
|
|
2771
2793
|
}
|
|
2772
2794
|
if (finalStatus === "committed") {
|
|
2773
|
-
const wasAutoCommitted = autoCommit === void 0 &&
|
|
2795
|
+
const wasAutoCommitted = autoCommit === void 0 && freshGovernanceMode === "open";
|
|
2774
2796
|
lines.push("");
|
|
2775
2797
|
lines.push(`## Committed: ${finalEntryId}`);
|
|
2776
2798
|
if (wasAutoCommitted) {
|
|
@@ -3026,7 +3048,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3026
3048
|
const agentId = getAgentSessionId();
|
|
3027
3049
|
const createdBy = agentId ? `agent:${agentId}` : "capture";
|
|
3028
3050
|
const wsCtx = await getWorkspaceContext();
|
|
3029
|
-
const
|
|
3051
|
+
const freshGovernanceModeForBatch = await refreshWorkspaceGovernanceMode();
|
|
3052
|
+
const autoCommitApplied = shouldAutoCommitCapture(autoCommit, freshGovernanceModeForBatch);
|
|
3030
3053
|
const needsClassification = entries.map((e, i) => ({ ...e, index: i })).filter((e) => !e.collection);
|
|
3031
3054
|
let classificationMap = /* @__PURE__ */ new Map();
|
|
3032
3055
|
if (needsClassification.length > 0) {
|
|
@@ -3194,11 +3217,13 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3194
3217
|
collectionSlug: resolvedSlug,
|
|
3195
3218
|
entryId: entry.entryId ?? void 0,
|
|
3196
3219
|
name: entry.name,
|
|
3197
|
-
|
|
3220
|
+
// DEC-896 / BR-76: omit status — server resolves from governanceMode (Open→active, else draft).
|
|
3198
3221
|
data,
|
|
3199
3222
|
createdBy,
|
|
3200
3223
|
sessionId: agentId ?? void 0,
|
|
3201
3224
|
canonicalKey: entry.canonicalKey ?? void 0,
|
|
3225
|
+
// BR-76 S2: Distinguish batch provenance in retrospective metrics (path a).
|
|
3226
|
+
originDetailOverride: "mcp-batch-capture",
|
|
3202
3227
|
...entry.sourceRef ? { sourceRef: entry.sourceRef } : {},
|
|
3203
3228
|
...entry.sourceExcerpt ? { sourceExcerpt: entry.sourceExcerpt } : {},
|
|
3204
3229
|
...preview ? { preview: true } : {}
|
|
@@ -3210,8 +3235,18 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3210
3235
|
...result.warnings ?? []
|
|
3211
3236
|
];
|
|
3212
3237
|
resolveGapsForEntry(entry.name, result.entryId);
|
|
3213
|
-
|
|
3238
|
+
const batchWasAutoCommittedServerSide = result.status === "active";
|
|
3239
|
+
let finalStatus = batchWasAutoCommittedServerSide ? "committed" : "draft";
|
|
3214
3240
|
let commitError;
|
|
3241
|
+
if (batchWasAutoCommittedServerSide) {
|
|
3242
|
+
await recordSessionActivity({ entryModified: internalId });
|
|
3243
|
+
trackChainEntryCommitted(wsCtx.workspaceId, {
|
|
3244
|
+
entry_id: finalEntryId,
|
|
3245
|
+
collection: resolvedSlug ?? void 0,
|
|
3246
|
+
commit_method: "auto",
|
|
3247
|
+
surface: "mcp_capture"
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3215
3250
|
let autoLinkCount = 0;
|
|
3216
3251
|
let entryOverlapCount = 0;
|
|
3217
3252
|
const searchQuery = extractSearchTerms(entry.name, entry.description);
|
|
@@ -3241,7 +3276,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3241
3276
|
} catch {
|
|
3242
3277
|
}
|
|
3243
3278
|
}
|
|
3244
|
-
if (autoCommitApplied) {
|
|
3279
|
+
if (autoCommitApplied && !batchWasAutoCommittedServerSide) {
|
|
3245
3280
|
try {
|
|
3246
3281
|
const semanticConflicts = await runSemanticConflictPreflight(entry.name, entry.description, resolvedSlug);
|
|
3247
3282
|
const blockingConflicts = semanticConflicts.filter(
|
|
@@ -3268,6 +3303,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3268
3303
|
}
|
|
3269
3304
|
} catch (error) {
|
|
3270
3305
|
commitError = error instanceof Error ? error.message : String(error);
|
|
3306
|
+
finalStatus = "draft_on_failure";
|
|
3307
|
+
await recordCommitFailure({ entryId: finalEntryId, error, sessionId: agentId, server });
|
|
3271
3308
|
}
|
|
3272
3309
|
}
|
|
3273
3310
|
const entryNorm = result.normalization;
|
|
@@ -3310,7 +3347,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3310
3347
|
const committed = created.filter((r) => r.status === "committed");
|
|
3311
3348
|
const proposed = created.filter((r) => r.status === "proposed");
|
|
3312
3349
|
const drafts = created.filter((r) => r.status === "draft");
|
|
3313
|
-
const
|
|
3350
|
+
const commitFailed = created.filter((r) => r.status === "draft_on_failure");
|
|
3314
3351
|
const classifiedCount = created.filter((r) => r.classifiedBy && r.classifiedBy !== "explicit").length;
|
|
3315
3352
|
await server.sendLoggingMessage({
|
|
3316
3353
|
level: "info",
|
|
@@ -3333,8 +3370,9 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
3333
3370
|
lines.push("");
|
|
3334
3371
|
}
|
|
3335
3372
|
if (created.length > 0) {
|
|
3373
|
+
const commitFailedSegment = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
|
|
3336
3374
|
lines.push(
|
|
3337
|
-
`**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft.`
|
|
3375
|
+
`**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedSegment}.`
|
|
3338
3376
|
);
|
|
3339
3377
|
lines.push("");
|
|
3340
3378
|
}
|
|
@@ -3378,10 +3416,10 @@ _Use \`move-entry\` to correct any misclassified entries._`);
|
|
|
3378
3416
|
lines.push("");
|
|
3379
3417
|
lines.push("_Re-capture these with an explicit `collection` or use the suggested collection._");
|
|
3380
3418
|
}
|
|
3381
|
-
if (
|
|
3419
|
+
if (commitFailed.length > 0) {
|
|
3382
3420
|
lines.push("");
|
|
3383
3421
|
lines.push("## Saved as draft");
|
|
3384
|
-
for (const r of
|
|
3422
|
+
for (const r of commitFailed) {
|
|
3385
3423
|
lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 ${r.commitError}`);
|
|
3386
3424
|
}
|
|
3387
3425
|
}
|
|
@@ -3432,7 +3470,8 @@ _Use \`move-entry\` to correct any misclassified entries._`);
|
|
|
3432
3470
|
}
|
|
3433
3471
|
const skippedNote = skippedLowConfidence.length > 0 ? `, ${skippedLowConfidence.length} skipped (low confidence)` : "";
|
|
3434
3472
|
const classifiedNote = classifiedCount > 0 ? `, ${classifiedCount} auto-classified` : "";
|
|
3435
|
-
const
|
|
3473
|
+
const commitFailedNote = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
|
|
3474
|
+
const summary = failed.length > 0 || skippedLowConfidence.length > 0 || commitFailed.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedNote}${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
|
|
3436
3475
|
const firstDraft = drafts[0];
|
|
3437
3476
|
const next = [];
|
|
3438
3477
|
if (created.length > 0) {
|
|
@@ -3464,13 +3503,15 @@ _Use \`move-entry\` to correct any misclassified entries._`);
|
|
|
3464
3503
|
...r.confidence != null ? { confidence: r.confidence } : {},
|
|
3465
3504
|
...r.confidenceTier ? { confidenceTier: r.confidenceTier } : {},
|
|
3466
3505
|
...r.warnings?.length ? { warnings: r.warnings } : {},
|
|
3467
|
-
...r.normalization ? { normalization: r.normalization } : {}
|
|
3506
|
+
...r.normalization ? { normalization: r.normalization } : {},
|
|
3507
|
+
...r.commitError ? { commitError: r.commitError } : {}
|
|
3468
3508
|
})),
|
|
3469
3509
|
total: created.length,
|
|
3470
3510
|
failed: failed.length,
|
|
3471
3511
|
committed: committed.length,
|
|
3472
3512
|
proposed: proposed.length,
|
|
3473
3513
|
drafts: drafts.length,
|
|
3514
|
+
...commitFailed.length > 0 ? { commitFailed: commitFailed.length } : {},
|
|
3474
3515
|
...classifiedCount > 0 ? { classified: classifiedCount } : {},
|
|
3475
3516
|
autoCommitApplied,
|
|
3476
3517
|
...skippedLowConfidence.length > 0 && {
|
|
@@ -3500,6 +3541,35 @@ _Use \`move-entry\` to correct any misclassified entries._`);
|
|
|
3500
3541
|
);
|
|
3501
3542
|
trackWriteTool(batchCaptureTool);
|
|
3502
3543
|
}
|
|
3544
|
+
async function recordCommitFailure({
|
|
3545
|
+
entryId,
|
|
3546
|
+
error,
|
|
3547
|
+
sessionId,
|
|
3548
|
+
server
|
|
3549
|
+
}) {
|
|
3550
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3551
|
+
const logLine = `commitEntry failed for ${entryId}: ${errorMessage}${sessionId ? ` (session: ${sessionId})` : ""}`;
|
|
3552
|
+
try {
|
|
3553
|
+
await server.sendLoggingMessage({ level: "error", data: logLine, logger: "product-brain" });
|
|
3554
|
+
} catch {
|
|
3555
|
+
}
|
|
3556
|
+
if (sessionId) {
|
|
3557
|
+
try {
|
|
3558
|
+
const tenName = `commitEntry failed for ${entryId} \u2014 ${errorMessage}`.slice(0, 250);
|
|
3559
|
+
await kernelMutation("chain.createEntry", {
|
|
3560
|
+
collectionSlug: "tensions",
|
|
3561
|
+
name: tenName,
|
|
3562
|
+
// Failure audit TENs intentionally stay draft — they need explicit human review,
|
|
3563
|
+
// not auto-commit, even in Open mode.
|
|
3564
|
+
status: "draft",
|
|
3565
|
+
data: {},
|
|
3566
|
+
createdBy: `agent:${sessionId}`,
|
|
3567
|
+
sessionId
|
|
3568
|
+
});
|
|
3569
|
+
} catch {
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3503
3573
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
3504
3574
|
"the",
|
|
3505
3575
|
"and",
|
|
@@ -7849,8 +7919,8 @@ function registerSessionTools(server) {
|
|
|
7849
7919
|
server.registerTool(
|
|
7850
7920
|
"session",
|
|
7851
7921
|
{
|
|
7852
|
-
title: "Session",
|
|
7853
|
-
description: "
|
|
7922
|
+
title: "Session \u2014 Low-Level Lifecycle",
|
|
7923
|
+
description: "Low-level session lifecycle management. **For user-facing 'begin a session', call `start_pb` instead** \u2014 it is the canonical opener and handles session start automatically.\n\nThree actions:\n\n- **start**: Low-level \u2014 begin a tracked session for attribution. Most agents should NOT call this directly; use `start_pb` which wraps it with stage-aware context. Use `session start` only for explicit session control (e.g. supersession after CLI work).\n- **close**: End the current session. Records structured data (entries created, modified, relations). If uncommitted drafts exist and wrapup hasn't run, shows a review summary. Run session-wrapup before closing.\n- **status**: Check current session \u2014 active or not, oriented, activity counts.",
|
|
7854
7924
|
inputSchema: sessionSchema,
|
|
7855
7925
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
7856
7926
|
},
|
|
@@ -10436,128 +10506,41 @@ function registerVerifyTools(server) {
|
|
|
10436
10506
|
trackWriteTool(verifyEntryTool);
|
|
10437
10507
|
}
|
|
10438
10508
|
|
|
10439
|
-
// src/tools/
|
|
10509
|
+
// src/tools/start_pb.ts
|
|
10440
10510
|
import { z as z17 } from "zod";
|
|
10441
10511
|
|
|
10442
|
-
// src/
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
{
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
{ key: "rationale", label: "Rationale", type: "string", searchable: true },
|
|
10475
|
-
{ key: "domain", label: "Domain", type: "string" },
|
|
10476
|
-
{ key: "severity", label: "Severity", type: "select", options: ["low", "medium", "high", "critical"] },
|
|
10477
|
-
{ key: "platforms", label: "Platforms", type: "array" }
|
|
10478
|
-
]
|
|
10479
|
-
}
|
|
10480
|
-
];
|
|
10481
|
-
var COLLECTION_PRESETS = [
|
|
10482
|
-
{
|
|
10483
|
-
id: "software-product",
|
|
10484
|
-
name: "Software Product",
|
|
10485
|
-
description: "For teams building software products \u2014 glossary, features, architecture, tech debt, and API endpoints",
|
|
10486
|
-
collections: [
|
|
10487
|
-
...GOVERNANCE_CORE,
|
|
10488
|
-
{ slug: "glossary", name: "Glossary", description: "Canonical terminology for the product domain", idPrefix: "GLO", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "select", options: ["Platform & Architecture", "Knowledge Management", "AI & Developer Tools", "Governance & Process"] }, { key: "confusedWith", label: "Confused With", type: "array" }] },
|
|
10489
|
-
{ slug: "features", name: "Features", description: "Product features and capabilities", idPrefix: "FEAT", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "scope", label: "Scope", type: "string" }, { key: "area", label: "Area", type: "string" }, { key: "status", label: "Status", type: "select", options: ["proposed", "in-progress", "shipped", "deprecated"] }] },
|
|
10490
|
-
{ slug: "architecture", name: "Architecture", description: "System architecture layers and components", idPrefix: "ARCH", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "layer", label: "Layer", type: "string" }, { key: "dependencies", label: "Dependencies", type: "array" }] },
|
|
10491
|
-
{ slug: "tech-debt", name: "Tech Debt", description: "Technical debt items to track and address", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "severity", label: "Severity", type: "select", options: ["low", "medium", "high", "critical"] }, { key: "area", label: "Area", type: "string" }, { key: "effort", label: "Effort", type: "select", options: ["small", "medium", "large"] }] },
|
|
10492
|
-
{ slug: "api-endpoints", name: "API Endpoints", description: "REST/GraphQL endpoints and their contracts", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "method", label: "Method", type: "select", options: ["GET", "POST", "PUT", "PATCH", "DELETE"] }, { key: "path", label: "Path", type: "string" }, { key: "auth", label: "Auth Required", type: "select", options: ["none", "api-key", "bearer", "session"] }] },
|
|
10493
|
-
{ slug: "decisions", name: "Decisions", description: "Significant decisions with rationale and context", idPrefix: "DEC", fields: [{ key: "rationale", label: "Rationale", type: "string", required: true, searchable: true }, { key: "date", label: "Date", type: "string" }, { key: "decidedBy", label: "Decided By", type: "string" }, { key: "alternatives", label: "Alternatives", type: "string" }] },
|
|
10494
|
-
{ slug: "tensions", name: "Tensions", description: "Friction points, pain points, and unmet needs", idPrefix: "TEN", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "priority", label: "Priority", type: "select", options: ["low", "medium", "high", "critical"] }, { key: "severity", label: "Severity", type: "select", options: ["low", "medium", "high", "critical"] }] }
|
|
10495
|
-
]
|
|
10496
|
-
},
|
|
10497
|
-
{
|
|
10498
|
-
id: "content-business",
|
|
10499
|
-
name: "Content Business",
|
|
10500
|
-
description: "For content creators, publishers, and media companies \u2014 topics, audience segments, content calendar, and brand voice",
|
|
10501
|
-
collections: [
|
|
10502
|
-
...GOVERNANCE_CORE,
|
|
10503
|
-
{ slug: "glossary", name: "Glossary", description: "Industry terminology and brand language", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "string" }] },
|
|
10504
|
-
{ slug: "topics", name: "Topics", description: "Content topics and themes", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "pillar", label: "Content Pillar", type: "string" }, { key: "stage", label: "Stage", type: "select", options: ["idea", "researching", "drafting", "published", "evergreen"] }] },
|
|
10505
|
-
{ slug: "audience-segments", name: "Audience Segments", description: "Target reader/viewer personas", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "painPoints", label: "Pain Points", type: "string" }, { key: "channels", label: "Channels", type: "array" }] },
|
|
10506
|
-
{ slug: "brand-voice", name: "Brand Voice", description: "Tone, style, and voice guidelines", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "doThis", label: "Do This", type: "string" }, { key: "notThis", label: "Not This", type: "string" }] },
|
|
10507
|
-
{ slug: "decisions", name: "Decisions", description: "Editorial and strategic decisions", fields: [{ key: "rationale", label: "Rationale", type: "string", required: true, searchable: true }, { key: "date", label: "Date", type: "string" }, { key: "decidedBy", label: "Decided By", type: "string" }] },
|
|
10508
|
-
{ slug: "tensions", name: "Tensions", description: "Content gaps, audience friction, and unmet needs", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "priority", label: "Priority", type: "select", options: ["low", "medium", "high", "critical"] }] }
|
|
10509
|
-
]
|
|
10510
|
-
},
|
|
10511
|
-
{
|
|
10512
|
-
id: "agency",
|
|
10513
|
-
name: "Agency",
|
|
10514
|
-
description: "For agencies managing multiple clients \u2014 client profiles, project scopes, deliverables, and processes",
|
|
10515
|
-
collections: [
|
|
10516
|
-
...GOVERNANCE_CORE,
|
|
10517
|
-
{ slug: "glossary", name: "Glossary", description: "Agency and client terminology", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "string" }] },
|
|
10518
|
-
{ slug: "clients", name: "Clients", description: "Client profiles and relationship context", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "industry", label: "Industry", type: "string" }, { key: "contact", label: "Primary Contact", type: "string" }] },
|
|
10519
|
-
{ slug: "deliverables", name: "Deliverables", description: "Standard deliverable types and templates", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "type", label: "Type", type: "string" }, { key: "estimatedHours", label: "Estimated Hours", type: "string" }] },
|
|
10520
|
-
{ slug: "processes", name: "Processes", description: "Standard operating procedures and workflows", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "steps", label: "Steps", type: "string" }, { key: "owner", label: "Owner", type: "string" }] },
|
|
10521
|
-
{ slug: "decisions", name: "Decisions", description: "Strategic and operational decisions", fields: [{ key: "rationale", label: "Rationale", type: "string", required: true, searchable: true }, { key: "date", label: "Date", type: "string" }] },
|
|
10522
|
-
{ slug: "tensions", name: "Tensions", description: "Process bottlenecks and client friction", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "priority", label: "Priority", type: "select", options: ["low", "medium", "high", "critical"] }] }
|
|
10523
|
-
]
|
|
10524
|
-
},
|
|
10525
|
-
{
|
|
10526
|
-
id: "saas-api",
|
|
10527
|
-
name: "SaaS API",
|
|
10528
|
-
description: "For API-first SaaS products \u2014 endpoints, schemas, rate limits, changelog, and integration guides",
|
|
10529
|
-
collections: [
|
|
10530
|
-
...GOVERNANCE_CORE,
|
|
10531
|
-
{ slug: "glossary", name: "Glossary", description: "API and domain terminology", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "string" }] },
|
|
10532
|
-
{ slug: "api-endpoints", name: "API Endpoints", description: "API routes and contracts", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "method", label: "Method", type: "select", options: ["GET", "POST", "PUT", "PATCH", "DELETE"] }, { key: "path", label: "Path", type: "string" }, { key: "auth", label: "Auth", type: "select", options: ["none", "api-key", "bearer", "oauth"] }] },
|
|
10533
|
-
{ slug: "schemas", name: "Schemas", description: "Data models and API schemas", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "format", label: "Format", type: "select", options: ["json", "protobuf", "graphql", "openapi"] }, { key: "version", label: "Version", type: "string" }] },
|
|
10534
|
-
{ slug: "rate-limits", name: "Rate Limits", description: "Rate limiting policies and tiers", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "tier", label: "Tier", type: "string" }, { key: "limit", label: "Limit", type: "string" }] },
|
|
10535
|
-
{ slug: "changelog", name: "Changelog", description: "API version history and breaking changes", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "version", label: "Version", type: "string" }, { key: "date", label: "Date", type: "string" }, { key: "breaking", label: "Breaking", type: "select", options: ["yes", "no"] }] },
|
|
10536
|
-
{ slug: "decisions", name: "Decisions", description: "API design decisions and rationale", fields: [{ key: "rationale", label: "Rationale", type: "string", required: true, searchable: true }, { key: "date", label: "Date", type: "string" }] }
|
|
10537
|
-
]
|
|
10538
|
-
},
|
|
10539
|
-
{
|
|
10540
|
-
id: "general",
|
|
10541
|
-
name: "General",
|
|
10542
|
-
description: "A minimal starter set \u2014 glossary, decisions, tensions, and governance. Add more collections as you need them.",
|
|
10543
|
-
collections: [
|
|
10544
|
-
...GOVERNANCE_CORE,
|
|
10545
|
-
{ slug: "glossary", name: "Glossary", description: "Canonical terminology", fields: [{ key: "canonical", label: "Canonical", type: "string", required: true, searchable: true }, { key: "category", label: "Category", type: "string" }] },
|
|
10546
|
-
{ slug: "decisions", name: "Decisions", description: "Decisions with rationale", fields: [{ key: "rationale", label: "Rationale", type: "string", required: true, searchable: true }, { key: "date", label: "Date", type: "string" }, { key: "decidedBy", label: "Decided By", type: "string" }] },
|
|
10547
|
-
{ slug: "tensions", name: "Tensions", description: "Friction points and unmet needs", fields: [{ key: "description", label: "Description", type: "string", required: true, searchable: true }, { key: "priority", label: "Priority", type: "select", options: ["low", "medium", "high", "critical"] }] }
|
|
10548
|
-
]
|
|
10549
|
-
}
|
|
10550
|
-
];
|
|
10551
|
-
function getPreset(id) {
|
|
10552
|
-
return COLLECTION_PRESETS.find((p) => p.id === id);
|
|
10553
|
-
}
|
|
10554
|
-
function listPresets() {
|
|
10555
|
-
return COLLECTION_PRESETS.map((p) => ({
|
|
10556
|
-
id: p.id,
|
|
10557
|
-
name: p.name,
|
|
10558
|
-
description: p.description,
|
|
10559
|
-
collectionCount: p.collections.length
|
|
10560
|
-
}));
|
|
10512
|
+
// src/tools/skills.ts
|
|
10513
|
+
import { z as z16 } from "zod";
|
|
10514
|
+
var skillsSchema = z16.object({
|
|
10515
|
+
entryId: z16.string().min(1).describe(
|
|
10516
|
+
"Workspace-scoped skill entry id (e.g. 'SKILL-pb-setup'). The tool refuses non-skill entries (canonicalKey !== 'skill') with INVALID_KIND."
|
|
10517
|
+
)
|
|
10518
|
+
});
|
|
10519
|
+
async function loadSkillBody(entryId) {
|
|
10520
|
+
const workspaceId = await getWorkspaceId();
|
|
10521
|
+
return await kernelQuery("setup.getSkillBody", {
|
|
10522
|
+
workspaceId,
|
|
10523
|
+
entryId
|
|
10524
|
+
});
|
|
10525
|
+
}
|
|
10526
|
+
function registerSkillsTools(server) {
|
|
10527
|
+
server.registerTool(
|
|
10528
|
+
"skills",
|
|
10529
|
+
{
|
|
10530
|
+
title: "Load workspace skill body",
|
|
10531
|
+
description: "Loads the markdown body of a SKILL-* entry from the current workspace. Generic loader: works for ANY skill in your workspace \u2014 no allow-list, no pb-setup-specific behavior. Use this when an agent or MCP client needs the canonical body of a skill (e.g. SKILL-pb-setup, SKILL-shape, SKILL-review). Returns markdown plus name + retrievedAt for caching.",
|
|
10532
|
+
inputSchema: skillsSchema,
|
|
10533
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
10534
|
+
},
|
|
10535
|
+
thinWrapper(async ({ entryId }) => {
|
|
10536
|
+
const result = await loadSkillBody(entryId);
|
|
10537
|
+
const summary = `Loaded ${result.entryId} (${result.body.length} chars).`;
|
|
10538
|
+
return {
|
|
10539
|
+
content: textContent(result.body),
|
|
10540
|
+
structuredContent: success(summary, result)
|
|
10541
|
+
};
|
|
10542
|
+
})
|
|
10543
|
+
);
|
|
10561
10544
|
}
|
|
10562
10545
|
|
|
10563
10546
|
// src/tools/planned-work.ts
|
|
@@ -11015,75 +10998,6 @@ function formatWhatNeedsAttentionBrief(wna) {
|
|
|
11015
10998
|
return lines;
|
|
11016
10999
|
}
|
|
11017
11000
|
|
|
11018
|
-
// src/tools/start-interview.ts
|
|
11019
|
-
import { z as z16 } from "zod";
|
|
11020
|
-
var interviewExtractionSchema = z16.object({
|
|
11021
|
-
vision: z16.string().min(10).describe("What they're building \u2014 concise product purpose statement"),
|
|
11022
|
-
audience: z16.string().optional().describe("Who it's for \u2014 primary user or customer segment"),
|
|
11023
|
-
techStack: z16.array(z16.string()).optional().describe("Key technologies, frameworks, or platforms (e.g. ['SvelteKit', 'Convex', 'PostgreSQL'])"),
|
|
11024
|
-
keyTerms: z16.array(z16.string()).optional().describe("Domain-specific terms that belong in the glossary"),
|
|
11025
|
-
keyDecisions: z16.array(z16.string()).optional().describe("Recent significant decisions made (each as a concise statement)"),
|
|
11026
|
-
tensions: z16.array(z16.string()).optional().describe("Pain points or friction the product is solving")
|
|
11027
|
-
});
|
|
11028
|
-
function getInterviewInstructions(workspaceName) {
|
|
11029
|
-
return {
|
|
11030
|
-
systemPrompt: `You are activating the **${workspaceName}** Product Brain. Ask 1\u20132 focused questions, then extract structured knowledge and batch-capture it. Let the user talk naturally \u2014 you do the structuring. In Open governance mode, entries commit automatically. In consensus/role mode, they stay as drafts for review.`,
|
|
11031
|
-
question1: `**What are you building, and who is it for?** A sentence or two is plenty \u2014 I'll pull out the structure.`,
|
|
11032
|
-
question2: `**What's one word or phrase that would trip someone up if they didn't know your world?** (That becomes your first glossary term \u2014 the one that saves explanations later.)`,
|
|
11033
|
-
extractionGuidance: `After the user answers, extract:
|
|
11034
|
-
- vision: the core product purpose (required)
|
|
11035
|
-
- audience: who it's for (optional)
|
|
11036
|
-
- techStack: technologies mentioned (optional, array)
|
|
11037
|
-
- keyTerms: domain terms \u2014 Q2 answer goes here (optional, array)
|
|
11038
|
-
- keyDecisions: any decisions stated (optional, array)
|
|
11039
|
-
- tensions: any pain points stated (optional, array)
|
|
11040
|
-
|
|
11041
|
-
Map to batch-capture entries:
|
|
11042
|
-
- vision \u2192 strategy ("Product Vision")
|
|
11043
|
-
- audience \u2192 audiences (1 entry)
|
|
11044
|
-
- techStack \u2192 architecture ("Tech Stack") + top 3 terms \u2192 glossary
|
|
11045
|
-
- keyTerms \u2192 glossary (1 per term)
|
|
11046
|
-
- keyDecisions \u2192 decisions (1 per decision)
|
|
11047
|
-
- tensions \u2192 tensions (1 per tension)`,
|
|
11048
|
-
captureInstructions: `Call batch-capture with the extracted entries. Omit \`autoCommit\` to follow workspace governance automatically, or pass \`autoCommit: false\` if the user wants review-first. 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 individually.`,
|
|
11049
|
-
qualityNote: (
|
|
11050
|
-
// FEAT-149: Retrieval-First Proof Moment.
|
|
11051
|
-
// The aha is friction elimination ("I'll never have to explain this again"),
|
|
11052
|
-
// not recognition ("it remembered me"). Simulate a FUTURE session, not a recap.
|
|
11053
|
-
`After capturing, silently call graph action=suggest on the strategy or architecture entry to build connections.
|
|
11054
|
-
|
|
11055
|
-
**Then demonstrate the proof moment \u2014 this is the most important step:**
|
|
11056
|
-
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.
|
|
11057
|
-
|
|
11058
|
-
Present with the "tomorrow" framing \u2014 simulate a future session, not a recap:
|
|
11059
|
-
"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:
|
|
11060
|
-
- [vision applied to that task \u2014 one sentence]
|
|
11061
|
-
- [ICP applied \u2014 who this feature is for in their context]
|
|
11062
|
-
- [any key term that matters for this task]
|
|
11063
|
-
|
|
11064
|
-
That's context you won't have to explain again."
|
|
11065
|
-
|
|
11066
|
-
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."`
|
|
11067
|
-
),
|
|
11068
|
-
scanOffer: `After the proof moment, offer the codebase scan:
|
|
11069
|
-
"Want me to learn more? I can read your project files \u2014 README, package.json, source structure \u2014 and pick up technical decisions, conventions, and architecture I missed. Takes about two minutes."`
|
|
11070
|
-
};
|
|
11071
|
-
}
|
|
11072
|
-
function buildInterviewResponse(workspaceName) {
|
|
11073
|
-
const instructions = getInterviewInstructions(workspaceName);
|
|
11074
|
-
return [
|
|
11075
|
-
"## Let's get to know your product",
|
|
11076
|
-
"",
|
|
11077
|
-
instructions.systemPrompt,
|
|
11078
|
-
"",
|
|
11079
|
-
"I'll ask you one or two questions. Your answers become the foundation of your Brain.",
|
|
11080
|
-
"",
|
|
11081
|
-
instructions.question1,
|
|
11082
|
-
"",
|
|
11083
|
-
"_Take your time \u2014 I'll pull out the structure from whatever you say._"
|
|
11084
|
-
].join("\n");
|
|
11085
|
-
}
|
|
11086
|
-
|
|
11087
11001
|
// src/lib/gapToPrompt.ts
|
|
11088
11002
|
function cleanLabel(label) {
|
|
11089
11003
|
return label.replace(/ has entries$/i, "").replace(/ coverage$/i, "").replace(/^Strategy — /i, "");
|
|
@@ -11111,28 +11025,19 @@ function formatTopGapPrompt(gap, remaining, ctx) {
|
|
|
11111
11025
|
];
|
|
11112
11026
|
return lines.join("\n");
|
|
11113
11027
|
}
|
|
11114
|
-
function formatGapList(gaps, limit = 3) {
|
|
11115
|
-
const topGaps = gaps.slice(0, limit);
|
|
11116
|
-
if (topGaps.length === 0) return [];
|
|
11117
|
-
const lines = [
|
|
11118
|
-
"Here's where your Brain would benefit most:",
|
|
11119
|
-
""
|
|
11120
|
-
];
|
|
11121
|
-
for (let i = 0; i < topGaps.length; i++) {
|
|
11122
|
-
const gap = topGaps[i];
|
|
11123
|
-
const prompt = gap.capabilityGuidance ?? gap.guidance;
|
|
11124
|
-
const label = cleanLabel(gap.label);
|
|
11125
|
-
lines.push(`${i + 1}. **${label}** \u2014 ${prompt}`);
|
|
11126
|
-
}
|
|
11127
|
-
return lines;
|
|
11128
|
-
}
|
|
11129
11028
|
function formatGapOneLiner(gap) {
|
|
11130
11029
|
if (!gap) return null;
|
|
11131
11030
|
const prompt = gap.capabilityGuidance ?? gap.guidance;
|
|
11132
11031
|
return `Next gap: ${prompt}`;
|
|
11133
11032
|
}
|
|
11134
11033
|
|
|
11135
|
-
// src/tools/
|
|
11034
|
+
// src/tools/start_pb.ts
|
|
11035
|
+
var startPbSchema = z17.object({
|
|
11036
|
+
task: z17.string().optional().describe(
|
|
11037
|
+
"What you're about to work on (e.g. 'implementing auth middleware'). Grounded/connected workspaces: filters governance to show relevant principles, standards, and business rules. Blank/seeded workspaces: ignored (setup flow takes over)."
|
|
11038
|
+
)
|
|
11039
|
+
});
|
|
11040
|
+
var PB_SETUP_SKILL_ENTRY_ID = "SKILL-pb-setup";
|
|
11136
11041
|
async function tryMarkOriented(agentSessionId, coherenceSnapshot) {
|
|
11137
11042
|
if (!agentSessionId) return { oriented: false, orientationStatus: "no_session" };
|
|
11138
11043
|
try {
|
|
@@ -11155,312 +11060,16 @@ async function tryMarkOriented(agentSessionId, coherenceSnapshot) {
|
|
|
11155
11060
|
}
|
|
11156
11061
|
}
|
|
11157
11062
|
}
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
task: z17.string().optional().describe(
|
|
11163
|
-
"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."
|
|
11164
|
-
)
|
|
11165
|
-
});
|
|
11166
|
-
function registerStartTools(server) {
|
|
11167
|
-
server.registerTool(
|
|
11168
|
-
"start",
|
|
11169
|
-
{
|
|
11170
|
-
title: "Start Product Brain",
|
|
11171
|
-
description: "The zero-friction entry point. Say 'start PB' to begin.\n\n- **Fresh workspace (blank)**: asks what you're building, captures the essentials, and can scan your codebase if useful.\n- **Early workspace (seeded)**: picks up where you left off with the top gaps to fill.\n- **Active workspace (grounded/connected)**: standup briefing with recent activity and open items.\n\nUse this as your first call. Replaces the need to call orient or health separately.",
|
|
11172
|
-
inputSchema: startSchema,
|
|
11173
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
11174
|
-
},
|
|
11175
|
-
thinWrapper(async ({ preset, task }) => {
|
|
11176
|
-
const errors = [];
|
|
11177
|
-
const agentSessionId = getAgentSessionId();
|
|
11178
|
-
let wsCtx = null;
|
|
11179
|
-
try {
|
|
11180
|
-
wsCtx = await getWorkspaceContext();
|
|
11181
|
-
} catch (e) {
|
|
11182
|
-
errors.push(`Workspace: ${e instanceof Error ? e.message : String(e)}`);
|
|
11183
|
-
}
|
|
11184
|
-
if (!wsCtx) {
|
|
11185
|
-
const text = "# Could not connect to Product Brain\n\n" + (errors.length > 0 ? errors.map((e) => `- ${e}`).join("\n") : "Check your API key and CONVEX_SITE_URL.");
|
|
11186
|
-
return {
|
|
11187
|
-
content: [{ type: "text", text }],
|
|
11188
|
-
structuredContent: failure(
|
|
11189
|
-
"BACKEND_UNAVAILABLE",
|
|
11190
|
-
"Could not connect to Product Brain.",
|
|
11191
|
-
"Check your API key and CONVEX_SITE_URL."
|
|
11192
|
-
)
|
|
11193
|
-
};
|
|
11194
|
-
}
|
|
11195
|
-
let stage = null;
|
|
11196
|
-
let readiness = null;
|
|
11197
|
-
try {
|
|
11198
|
-
readiness = await kernelQuery("chain.workspaceReadiness");
|
|
11199
|
-
stage = readiness?.stage ?? "blank";
|
|
11200
|
-
} catch {
|
|
11201
|
-
errors.push("Readiness check unavailable \u2014 showing workspace summary.");
|
|
11202
|
-
}
|
|
11203
|
-
if (stage === "blank" && preset) {
|
|
11204
|
-
const result = await seedPreset(wsCtx, preset, agentSessionId);
|
|
11205
|
-
return {
|
|
11206
|
-
content: [{ type: "text", text: result.text }],
|
|
11207
|
-
structuredContent: result.structuredContent
|
|
11208
|
-
};
|
|
11209
|
-
}
|
|
11210
|
-
if (stage === "blank") {
|
|
11211
|
-
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
11212
|
-
const blankResult = buildBlankResponse(wsCtx, {
|
|
11213
|
-
oriented,
|
|
11214
|
-
orientationStatus,
|
|
11215
|
-
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
11216
|
-
});
|
|
11217
|
-
return {
|
|
11218
|
-
content: [{ type: "text", text: blankResult.text }],
|
|
11219
|
-
structuredContent: blankResult.structuredContent
|
|
11220
|
-
};
|
|
11221
|
-
}
|
|
11222
|
-
if (stage === "seeded") {
|
|
11223
|
-
const result = await buildSeededResponse(wsCtx, readiness, agentSessionId);
|
|
11224
|
-
void kernelMutation("chain.setOnboardingCompleted", {}).catch(() => {
|
|
11225
|
-
});
|
|
11226
|
-
return {
|
|
11227
|
-
content: [{ type: "text", text: result.text }],
|
|
11228
|
-
structuredContent: result.structuredContent
|
|
11229
|
-
};
|
|
11230
|
-
}
|
|
11231
|
-
if (stage === "grounded" || stage === "connected") {
|
|
11232
|
-
void kernelMutation("chain.setOnboardingCompleted", {}).catch(() => {
|
|
11233
|
-
});
|
|
11234
|
-
}
|
|
11235
|
-
const orientResult = await buildOrientResponse(wsCtx, agentSessionId, errors, task);
|
|
11236
|
-
return {
|
|
11237
|
-
content: [{ type: "text", text: orientResult.text }],
|
|
11238
|
-
structuredContent: orientResult.structuredContent
|
|
11239
|
-
};
|
|
11240
|
-
})
|
|
11241
|
-
);
|
|
11242
|
-
}
|
|
11243
|
-
function buildBlankResponse(wsCtx, sessionCtx) {
|
|
11244
|
-
const instructions = getInterviewInstructions(wsCtx.workspaceName);
|
|
11245
|
-
const presetList = listPresets();
|
|
11246
|
-
const presets = presetList.map((p) => p.id).join(", ");
|
|
11247
|
-
const text = [
|
|
11248
|
-
// ── User-facing welcome — present this to the user ─────────────────
|
|
11249
|
-
`# Welcome to ${wsCtx.workspaceName}`,
|
|
11250
|
-
"",
|
|
11251
|
-
"Your workspace is fresh. Let's bring it to life.",
|
|
11252
|
-
"",
|
|
11253
|
-
"Tell me what you're building \u2014 paste a doc, describe it in a sentence, or say **scan my codebase**.",
|
|
11254
|
-
"",
|
|
11255
|
-
"_Powered by [Product Brain](https://productbrain.io) \u2014 your AI's product context layer._",
|
|
11256
|
-
"",
|
|
11257
|
-
"---",
|
|
11258
|
-
"",
|
|
11259
|
-
// ── Agent instructions — follow these exactly, do not present them ──
|
|
11260
|
-
"## Agent Instructions",
|
|
11261
|
-
"",
|
|
11262
|
-
"### Step 1 \u2014 Open with their name (FLO-1)",
|
|
11263
|
-
`Ask: _"What should I call you?"_ before asking about the product. Use their name throughout.`,
|
|
11264
|
-
"",
|
|
11265
|
-
"### Step 2 \u2014 Detect path from their first response (no menu, no numbered options)",
|
|
11266
|
-
"",
|
|
11267
|
-
"Read signals from the user's first substantive message and branch silently:",
|
|
11268
|
-
"",
|
|
11269
|
-
"**document** \u2014 paste >200 words, markdown headers, or says 'here's my doc/brief/spec'",
|
|
11270
|
-
"\u2192 Extract vision, audience, keyTerms, keyDecisions, tensions from the document.",
|
|
11271
|
-
"\u2192 Present a numbered confirmation list before capturing. Capture only confirmed entries.",
|
|
11272
|
-
"\u2192 Fallback: if sparse or unclear, switch to interview path.",
|
|
11273
|
-
"",
|
|
11274
|
-
"**codebase** \u2014 mentions 'scan', 'codebase', README, or provides a file path",
|
|
11275
|
-
`\u2192 First ask: _"Tell me one thing about this product that's not obvious from the code \u2014 what's it actually for?"_`,
|
|
11276
|
-
"\u2192 Then read: README.md, package.json / pyproject.toml / Cargo.toml, top-level source dirs.",
|
|
11277
|
-
"\u2192 Infer 5\u20138 entries. Present numbered list for confirmation. Capture only confirmed entries.",
|
|
11278
|
-
"\u2192 Fallback: if no README or empty project, immediately switch to interview path.",
|
|
11279
|
-
"",
|
|
11280
|
-
`**preset** \u2014 mentions a domain keyword (SaaS, agency, content business, API)`,
|
|
11281
|
-
`\u2192 Confirm which preset fits. Available: ${presets}.`,
|
|
11282
|
-
"\u2192 Call `start` with `preset: '<chosen-preset>'`.",
|
|
11283
|
-
"\u2192 Fallback: if no preset matches or seeding fails, switch to interview path.",
|
|
11284
|
-
"",
|
|
11285
|
-
"**interview** (default \u2014 1\u20135 sentence description or anything else)",
|
|
11286
|
-
`\u2192 ${instructions.systemPrompt}`,
|
|
11287
|
-
`\u2192 Q1: ${instructions.question1}`,
|
|
11288
|
-
`\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.)`,
|
|
11289
|
-
instructions.extractionGuidance,
|
|
11290
|
-
`\u2192 ${instructions.captureInstructions}`,
|
|
11291
|
-
"\u2192 Omit `autoCommit` \u2014 workspace governance applies automatically.",
|
|
11292
|
-
"",
|
|
11293
|
-
"### Step 3 \u2014 Proof moment (most important \u2014 run after every path)",
|
|
11294
|
-
"",
|
|
11295
|
-
instructions.qualityNote,
|
|
11296
|
-
"",
|
|
11297
|
-
"### Step 4 \u2014 Deepen (offer after proof moment)",
|
|
11298
|
-
"",
|
|
11299
|
-
instructions.scanOffer,
|
|
11300
|
-
"If they say yes, scan README.md, package.json/pyproject.toml/Cargo.toml, and top-level source dirs.",
|
|
11301
|
-
"Infer entries with the product context you now have. Present as a numbered list for confirmation.",
|
|
11302
|
-
"Capture only confirmed entries."
|
|
11303
|
-
].join("\n");
|
|
11304
|
-
return {
|
|
11305
|
-
text,
|
|
11306
|
-
structuredContent: success(
|
|
11307
|
-
"Workspace is blank. Ready for activation \u2014 interview, document, codebase scan, or preset.",
|
|
11308
|
-
{
|
|
11309
|
-
stage: "blank",
|
|
11310
|
-
interviewSchema: {
|
|
11311
|
-
fields: ["vision", "audience", "techStack", "keyTerms", "keyDecisions", "tensions"],
|
|
11312
|
-
mapping: {
|
|
11313
|
-
vision: "strategy",
|
|
11314
|
-
audience: "audiences",
|
|
11315
|
-
techStack: "architecture + top 3 terms \u2192 glossary",
|
|
11316
|
-
keyTerms: "glossary",
|
|
11317
|
-
keyDecisions: "decisions",
|
|
11318
|
-
tensions: "tensions"
|
|
11319
|
-
}
|
|
11320
|
-
},
|
|
11321
|
-
availablePresets: presetList.map((p) => ({ id: p.id, name: p.name })),
|
|
11322
|
-
...sessionCtx
|
|
11323
|
-
},
|
|
11324
|
-
[
|
|
11325
|
-
{ tool: "capture", description: "Capture product knowledge", parameters: {} },
|
|
11326
|
-
{ tool: "start", description: "Seed with a preset", parameters: { preset: "software-product" } }
|
|
11327
|
-
]
|
|
11328
|
-
)
|
|
11329
|
-
};
|
|
11330
|
-
}
|
|
11331
|
-
async function buildSeededResponse(wsCtx, readiness, agentSessionId) {
|
|
11332
|
-
const stage = readiness?.stage ?? "seeded";
|
|
11333
|
-
const score = readiness?.score ?? null;
|
|
11334
|
-
const gaps = readiness?.gaps ?? [];
|
|
11335
|
-
const lines = [
|
|
11336
|
-
`# ${wsCtx.workspaceName}`,
|
|
11337
|
-
"_Picking up where you left off._",
|
|
11338
|
-
""
|
|
11339
|
-
];
|
|
11340
|
-
if (gaps.length > 0) {
|
|
11341
|
-
const gapLines = formatGapList(gaps, 3);
|
|
11342
|
-
lines.push(...gapLines);
|
|
11343
|
-
lines.push("");
|
|
11344
|
-
lines.push("Pick any to start \u2014 or begin with **#1** and I'll guide you through it.");
|
|
11345
|
-
} else {
|
|
11346
|
-
lines.push("No gaps detected \u2014 your workspace is filling up nicely. What would you like to work on?");
|
|
11347
|
-
}
|
|
11348
|
-
const knowledgeGaps = getTopGaps(3);
|
|
11349
|
-
if (knowledgeGaps.length > 0) {
|
|
11350
|
-
lines.push("");
|
|
11351
|
-
lines.push("### Learning Opportunities");
|
|
11352
|
-
lines.push("_Topics agents recently looked for but aren't on the Chain yet:_");
|
|
11353
|
-
lines.push("");
|
|
11354
|
-
for (const gap of knowledgeGaps) {
|
|
11355
|
-
const freq = gap.count > 1 ? ` _(${gap.count}x)_` : "";
|
|
11356
|
-
lines.push(`- **${gap.query}**${freq}`);
|
|
11357
|
-
}
|
|
11358
|
-
lines.push("");
|
|
11359
|
-
lines.push("_Capturing these will make future sessions more effective._");
|
|
11360
|
-
}
|
|
11361
|
-
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
11362
|
-
const next = gaps.length > 0 ? [{ tool: "capture", description: `Fill gap: ${gaps[0].label}`, parameters: {} }] : [];
|
|
11363
|
-
return {
|
|
11364
|
-
text: lines.join("\n"),
|
|
11365
|
-
structuredContent: success(
|
|
11366
|
-
gaps.length > 0 ? `Workspace seeded. ${gaps.length} gap${gaps.length === 1 ? "" : "s"} to fill. Top: ${gaps[0].label}.` : `Workspace seeded. No gaps detected.`,
|
|
11367
|
-
{
|
|
11368
|
-
stage,
|
|
11369
|
-
readinessScore: score ?? null,
|
|
11370
|
-
totalGaps: gaps.length,
|
|
11371
|
-
topGapLabels: gaps.slice(0, 3).map((g) => g.label),
|
|
11372
|
-
oriented,
|
|
11373
|
-
orientationStatus,
|
|
11374
|
-
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
11375
|
-
},
|
|
11376
|
-
next.length > 0 ? next : void 0
|
|
11377
|
-
)
|
|
11378
|
-
};
|
|
11379
|
-
}
|
|
11380
|
-
async function seedPreset(wsCtx, presetId, agentSessionId) {
|
|
11381
|
-
const preset = getPreset(presetId);
|
|
11382
|
-
if (!preset) {
|
|
11383
|
-
return {
|
|
11384
|
-
text: `Preset "${presetId}" not found.
|
|
11385
|
-
|
|
11386
|
-
Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`,
|
|
11387
|
-
structuredContent: failure(
|
|
11388
|
-
"NOT_FOUND",
|
|
11389
|
-
`Preset '${presetId}' not found.`,
|
|
11390
|
-
`Available presets: ${listPresets().map((p) => p.id).join(", ")}.`,
|
|
11391
|
-
[{ tool: "start", description: "Start with a preset", parameters: { preset: "software-product" } }]
|
|
11392
|
-
)
|
|
11393
|
-
};
|
|
11394
|
-
}
|
|
11395
|
-
const seeded = [];
|
|
11396
|
-
const skipped = [];
|
|
11397
|
-
for (const col of preset.collections) {
|
|
11398
|
-
try {
|
|
11399
|
-
const purpose = col.purpose ?? col.description;
|
|
11400
|
-
await kernelCall("chain.createCollection", {
|
|
11401
|
-
slug: col.slug,
|
|
11402
|
-
name: col.name,
|
|
11403
|
-
description: col.description,
|
|
11404
|
-
purpose,
|
|
11405
|
-
icon: col.icon,
|
|
11406
|
-
idPrefix: col.idPrefix,
|
|
11407
|
-
fields: col.fields,
|
|
11408
|
-
createdBy: "preset:" + presetId
|
|
11409
|
-
});
|
|
11410
|
-
seeded.push(col.name);
|
|
11411
|
-
} catch {
|
|
11412
|
-
skipped.push(`${col.name} (already exists)`);
|
|
11413
|
-
}
|
|
11414
|
-
}
|
|
11415
|
-
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
11416
|
-
const lines = [
|
|
11417
|
-
`# ${wsCtx.workspaceName} is ready`,
|
|
11418
|
-
"",
|
|
11419
|
-
`Seeded **${preset.name}** preset with ${seeded.length} collection${seeded.length === 1 ? "" : "s"}:`,
|
|
11420
|
-
""
|
|
11421
|
-
];
|
|
11422
|
-
for (const name of seeded) {
|
|
11423
|
-
lines.push(`- ${name}`);
|
|
11424
|
-
}
|
|
11425
|
-
if (skipped.length > 0) {
|
|
11426
|
-
lines.push("", "Skipped (already exist):");
|
|
11427
|
-
for (const name of skipped) {
|
|
11428
|
-
lines.push(`- ${name}`);
|
|
11429
|
-
}
|
|
11430
|
-
}
|
|
11431
|
-
lines.push(
|
|
11432
|
-
"",
|
|
11433
|
-
buildInterviewResponse(wsCtx.workspaceName),
|
|
11434
|
-
"",
|
|
11435
|
-
"_You can also customize your collections anytime \u2014 just ask._"
|
|
11436
|
-
);
|
|
11437
|
-
return {
|
|
11438
|
-
text: lines.join("\n"),
|
|
11439
|
-
structuredContent: success(
|
|
11440
|
-
`Seeded '${preset.name}' preset with ${seeded.length} collection${seeded.length === 1 ? "" : "s"}.`,
|
|
11441
|
-
{
|
|
11442
|
-
stage: "blank",
|
|
11443
|
-
preset: presetId,
|
|
11444
|
-
seededCollections: seeded,
|
|
11445
|
-
skippedCollections: skipped,
|
|
11446
|
-
oriented,
|
|
11447
|
-
orientationStatus,
|
|
11448
|
-
...agentSessionId ? { sessionId: agentSessionId } : {}
|
|
11449
|
-
},
|
|
11450
|
-
[{ tool: "capture", description: "Capture your first entry", parameters: {} }]
|
|
11451
|
-
)
|
|
11452
|
-
};
|
|
11063
|
+
function isActiveNowBet(e) {
|
|
11064
|
+
const status = e.status ?? e.data?.status;
|
|
11065
|
+
const horizon = e.data?.horizon;
|
|
11066
|
+
return status === "active" && horizon === "now";
|
|
11453
11067
|
}
|
|
11454
11068
|
function computeWorkspaceAge(createdAt) {
|
|
11455
11069
|
if (!createdAt) return { ageDays: 0, isNeglected: false };
|
|
11456
11070
|
const ageDays = Math.floor((Date.now() - createdAt) / (1e3 * 60 * 60 * 24));
|
|
11457
11071
|
return { ageDays, isNeglected: ageDays >= 30 };
|
|
11458
11072
|
}
|
|
11459
|
-
function isActiveNowBet(e) {
|
|
11460
|
-
const status = e.status ?? e.data?.status;
|
|
11461
|
-
const horizon = e.data?.horizon;
|
|
11462
|
-
return status === "active" && horizon === "now";
|
|
11463
|
-
}
|
|
11464
11073
|
function pickNextTensionPrompt(openTensions) {
|
|
11465
11074
|
if (openTensions.length === 0) return null;
|
|
11466
11075
|
const t = openTensions[0];
|
|
@@ -11469,9 +11078,32 @@ function pickNextTensionPrompt(openTensions) {
|
|
|
11469
11078
|
cta: "Want to discuss this tension or capture a decision about it?"
|
|
11470
11079
|
};
|
|
11471
11080
|
}
|
|
11081
|
+
async function buildSetupResponse(agentSessionId) {
|
|
11082
|
+
await kernelMutation(
|
|
11083
|
+
"setup.stampMcpOnlySurface",
|
|
11084
|
+
{}
|
|
11085
|
+
);
|
|
11086
|
+
const skill = await loadSkillBody(PB_SETUP_SKILL_ENTRY_ID);
|
|
11087
|
+
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
11088
|
+
return {
|
|
11089
|
+
text: skill.body,
|
|
11090
|
+
structuredContent: success(
|
|
11091
|
+
`pb-setup ready. Delivering SKILL-pb-setup body (${skill.body.length} chars).`,
|
|
11092
|
+
{
|
|
11093
|
+
bootstrap: "pb-setup",
|
|
11094
|
+
skillEntryId: skill.entryId,
|
|
11095
|
+
skillBody: skill.body,
|
|
11096
|
+
stage: "setup",
|
|
11097
|
+
oriented,
|
|
11098
|
+
orientationStatus,
|
|
11099
|
+
...agentSessionId ? { sessionId: agentSessionId } : {},
|
|
11100
|
+
instructions: "Read skillBody and follow it; use commit-entry/entries/orient MCP tools per PAT-227."
|
|
11101
|
+
}
|
|
11102
|
+
)
|
|
11103
|
+
};
|
|
11104
|
+
}
|
|
11472
11105
|
async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
|
|
11473
|
-
const
|
|
11474
|
-
const { ageDays, isNeglected } = computeWorkspaceAge(wsFullCtx.createdAt);
|
|
11106
|
+
const { ageDays, isNeglected } = computeWorkspaceAge(wsCtx.createdAt ?? null);
|
|
11475
11107
|
let priorSessions = [];
|
|
11476
11108
|
let recoveryBlock = null;
|
|
11477
11109
|
try {
|
|
@@ -11496,7 +11128,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
|
|
|
11496
11128
|
const isLowReadiness = readiness !== null && readiness.score < 50;
|
|
11497
11129
|
const isHighReadiness = readiness !== null && readiness.score >= 50;
|
|
11498
11130
|
const stage = readiness?.stage ?? null;
|
|
11499
|
-
const captureBehaviorNote =
|
|
11131
|
+
const captureBehaviorNote = wsCtx.governanceMode === "open" ? "_In Open mode, user-authored captures commit immediately unless you ask me to keep them as drafts._" : "_Everything I capture stays pending until you confirm._";
|
|
11500
11132
|
const coherence = buildCoherenceSection();
|
|
11501
11133
|
let allCollections = [];
|
|
11502
11134
|
try {
|
|
@@ -11695,6 +11327,61 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
|
|
|
11695
11327
|
)
|
|
11696
11328
|
};
|
|
11697
11329
|
}
|
|
11330
|
+
function registerStartPbTools(server) {
|
|
11331
|
+
server.registerTool(
|
|
11332
|
+
"start_pb",
|
|
11333
|
+
{
|
|
11334
|
+
title: "Start Product Brain",
|
|
11335
|
+
description: "Universal session opener \u2014 say 'Start PB' to begin every session (PAT-227).\n\nStage-aware delivery:\n- **Fresh workspace (blank/seeded)**: returns the canonical SKILL-pb-setup body verbatim. The skill drives the install-to-activation flow.\n- **Active workspace (grounded/connected)**: standup briefing with active bets, governance, and recent activity.\n\nAlways call this first. It starts a session, stamps the surface, and loads the right guidance for your stage. Replaces both the former `start` tool and the former chat-only `start_pb` bootstrap.",
|
|
11336
|
+
inputSchema: startPbSchema,
|
|
11337
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
11338
|
+
},
|
|
11339
|
+
thinWrapper(async ({ task }) => {
|
|
11340
|
+
const errors = [];
|
|
11341
|
+
const agentSessionId = getAgentSessionId();
|
|
11342
|
+
let wsCtx = null;
|
|
11343
|
+
try {
|
|
11344
|
+
wsCtx = await getWorkspaceContext();
|
|
11345
|
+
} catch (e) {
|
|
11346
|
+
errors.push(`Workspace: ${e instanceof Error ? e.message : String(e)}`);
|
|
11347
|
+
}
|
|
11348
|
+
if (!wsCtx) {
|
|
11349
|
+
const text = "# Could not connect to Product Brain\n\n" + (errors.length > 0 ? errors.map((e) => `- ${e}`).join("\n") : "Check your API key and CONVEX_SITE_URL.");
|
|
11350
|
+
return {
|
|
11351
|
+
content: [{ type: "text", text }],
|
|
11352
|
+
structuredContent: failure(
|
|
11353
|
+
"BACKEND_UNAVAILABLE",
|
|
11354
|
+
"Could not connect to Product Brain.",
|
|
11355
|
+
"Check your API key and CONVEX_SITE_URL."
|
|
11356
|
+
)
|
|
11357
|
+
};
|
|
11358
|
+
}
|
|
11359
|
+
let stage = null;
|
|
11360
|
+
try {
|
|
11361
|
+
const readiness = await kernelQuery("chain.workspaceReadiness");
|
|
11362
|
+
stage = readiness?.stage ?? "blank";
|
|
11363
|
+
} catch {
|
|
11364
|
+
errors.push("Readiness check unavailable \u2014 showing workspace summary.");
|
|
11365
|
+
}
|
|
11366
|
+
if (stage === "blank" || stage === "seeded") {
|
|
11367
|
+
const result = await buildSetupResponse(agentSessionId);
|
|
11368
|
+
return {
|
|
11369
|
+
content: textContent(result.text),
|
|
11370
|
+
structuredContent: result.structuredContent
|
|
11371
|
+
};
|
|
11372
|
+
}
|
|
11373
|
+
if (stage === "grounded" || stage === "connected") {
|
|
11374
|
+
void kernelMutation("chain.setOnboardingCompleted", {}).catch(() => {
|
|
11375
|
+
});
|
|
11376
|
+
}
|
|
11377
|
+
const orientResult = await buildOrientResponse(wsCtx, agentSessionId, errors, task);
|
|
11378
|
+
return {
|
|
11379
|
+
content: [{ type: "text", text: orientResult.text }],
|
|
11380
|
+
structuredContent: orientResult.structuredContent
|
|
11381
|
+
};
|
|
11382
|
+
})
|
|
11383
|
+
);
|
|
11384
|
+
}
|
|
11698
11385
|
|
|
11699
11386
|
// src/tools/usage.ts
|
|
11700
11387
|
import { z as z18 } from "zod";
|
|
@@ -13062,139 +12749,141 @@ ${nodeDetail}${flowLines}`
|
|
|
13062
12749
|
return unknownAction(action, ["show", "explore", "flow"]);
|
|
13063
12750
|
})
|
|
13064
12751
|
);
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
|
|
13078
|
-
|
|
13079
|
-
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
12752
|
+
if (process.env.PB_INTERNAL === "true") {
|
|
12753
|
+
const archAdminTool = server.registerTool(
|
|
12754
|
+
"architecture-admin",
|
|
12755
|
+
{
|
|
12756
|
+
title: "Architecture Admin",
|
|
12757
|
+
description: "Architecture maintenance \u2014 seed the default architecture data or run a dependency health check.\n\nActions:\n- `seed`: Populate the architecture collection with the default Product OS map. Safe to re-run.\n- `check`: Scan the codebase for dependency direction violations against layer rules.",
|
|
12758
|
+
inputSchema: architectureAdminSchema,
|
|
12759
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
12760
|
+
},
|
|
12761
|
+
thinWrapper(async ({ action }) => {
|
|
12762
|
+
if (action === "seed") {
|
|
12763
|
+
await ensureCollection();
|
|
12764
|
+
const existing = await listArchEntries();
|
|
12765
|
+
const existingIds = new Set(existing.map((e) => e.entryId));
|
|
12766
|
+
let created = 0;
|
|
12767
|
+
let updated = 0;
|
|
12768
|
+
let unchanged = 0;
|
|
12769
|
+
const allWarnings = [];
|
|
12770
|
+
const allSeeds = [
|
|
12771
|
+
{ ...SEED_TEMPLATE, order: 0, status: "active" },
|
|
12772
|
+
...SEED_LAYERS.map((l) => ({ ...l, status: "active" })),
|
|
12773
|
+
...SEED_NODES.map((n) => ({ ...n, status: "active" })),
|
|
12774
|
+
...SEED_FLOWS.map((f) => ({ ...f, status: "active" }))
|
|
12775
|
+
];
|
|
12776
|
+
for (const seed of allSeeds) {
|
|
12777
|
+
if (existingIds.has(seed.entryId)) {
|
|
12778
|
+
const existingEntry = existing.find((e) => e.entryId === seed.entryId);
|
|
12779
|
+
const existingData = existingEntry?.data ?? {};
|
|
12780
|
+
const seedData = seed.data;
|
|
12781
|
+
const hasChanges = Object.keys(seedData).some(
|
|
12782
|
+
(k) => seedData[k] !== void 0 && existingData[k] !== seedData[k]
|
|
12783
|
+
);
|
|
12784
|
+
if (hasChanges) {
|
|
12785
|
+
const mergedData = { ...existingData, ...seedData };
|
|
12786
|
+
const updateResult = await kernelMutation("chain.updateEntry", { entryId: seed.entryId, data: mergedData });
|
|
12787
|
+
if (updateResult.normalizationWarnings.length > 0 || updateResult.validationWarnings.length > 0) {
|
|
12788
|
+
allWarnings.push({
|
|
12789
|
+
entryId: seed.entryId,
|
|
12790
|
+
normalizationWarnings: updateResult.normalizationWarnings,
|
|
12791
|
+
validationWarnings: updateResult.validationWarnings
|
|
12792
|
+
});
|
|
12793
|
+
}
|
|
12794
|
+
updated++;
|
|
12795
|
+
} else {
|
|
12796
|
+
unchanged++;
|
|
13105
12797
|
}
|
|
13106
|
-
|
|
13107
|
-
} else {
|
|
13108
|
-
unchanged++;
|
|
12798
|
+
continue;
|
|
13109
12799
|
}
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
const createResult = await kernelMutation("chain.createEntry", {
|
|
13113
|
-
collectionSlug: COLLECTION_SLUG,
|
|
13114
|
-
entryId: seed.entryId,
|
|
13115
|
-
name: seed.name,
|
|
13116
|
-
status: seed.status,
|
|
13117
|
-
data: seed.data,
|
|
13118
|
-
order: seed.order ?? 0
|
|
13119
|
-
});
|
|
13120
|
-
if (createResult.normalizationWarnings.length > 0 || createResult.validationWarnings.length > 0) {
|
|
13121
|
-
allWarnings.push({
|
|
12800
|
+
const createResult = await kernelMutation("chain.createEntry", {
|
|
12801
|
+
collectionSlug: COLLECTION_SLUG,
|
|
13122
12802
|
entryId: seed.entryId,
|
|
13123
|
-
|
|
13124
|
-
|
|
12803
|
+
name: seed.name,
|
|
12804
|
+
status: seed.status,
|
|
12805
|
+
data: seed.data,
|
|
12806
|
+
order: seed.order ?? 0
|
|
13125
12807
|
});
|
|
12808
|
+
if (createResult.normalizationWarnings.length > 0 || createResult.validationWarnings.length > 0) {
|
|
12809
|
+
allWarnings.push({
|
|
12810
|
+
entryId: seed.entryId,
|
|
12811
|
+
normalizationWarnings: createResult.normalizationWarnings,
|
|
12812
|
+
validationWarnings: createResult.validationWarnings
|
|
12813
|
+
});
|
|
12814
|
+
}
|
|
12815
|
+
created++;
|
|
13126
12816
|
}
|
|
13127
|
-
|
|
13128
|
-
}
|
|
13129
|
-
let responseText = `# Architecture Seeded
|
|
12817
|
+
let responseText = `# Architecture Seeded
|
|
13130
12818
|
|
|
13131
12819
|
**Created:** ${created} entries
|
|
13132
12820
|
**Updated:** ${updated} (merged new fields)
|
|
13133
12821
|
**Unchanged:** ${unchanged}
|
|
13134
12822
|
|
|
13135
12823
|
Use \`architecture action=show\` to view the map.`;
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
12824
|
+
if (allWarnings.length > 0) {
|
|
12825
|
+
responseText += "\n\n---\n\n\u26A0\uFE0F **Seed warnings:**";
|
|
12826
|
+
for (const w of allWarnings) {
|
|
12827
|
+
for (const nw of w.normalizationWarnings) {
|
|
12828
|
+
responseText += `
|
|
13141
12829
|
- **${w.entryId}** (normalization): ${nw}`;
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
12830
|
+
}
|
|
12831
|
+
for (const vw of w.validationWarnings) {
|
|
12832
|
+
responseText += `
|
|
13145
12833
|
- **${w.entryId}** (validation): ${vw}`;
|
|
12834
|
+
}
|
|
13146
12835
|
}
|
|
13147
12836
|
}
|
|
13148
|
-
}
|
|
13149
|
-
return {
|
|
13150
|
-
content: [{
|
|
13151
|
-
type: "text",
|
|
13152
|
-
text: responseText
|
|
13153
|
-
}],
|
|
13154
|
-
structuredContent: success(
|
|
13155
|
-
`Architecture seeded: ${created} created, ${updated} updated, ${unchanged} unchanged.`,
|
|
13156
|
-
{ created, updated, unchanged },
|
|
13157
|
-
[{ tool: "architecture", description: "View the map", parameters: { action: "show" } }]
|
|
13158
|
-
)
|
|
13159
|
-
};
|
|
13160
|
-
}
|
|
13161
|
-
if (action === "check") {
|
|
13162
|
-
const projectRoot = resolveProjectRoot();
|
|
13163
|
-
if (!projectRoot) {
|
|
13164
12837
|
return {
|
|
13165
12838
|
content: [{
|
|
13166
12839
|
type: "text",
|
|
13167
|
-
text:
|
|
12840
|
+
text: responseText
|
|
13168
12841
|
}],
|
|
13169
|
-
structuredContent:
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
12842
|
+
structuredContent: success(
|
|
12843
|
+
`Architecture seeded: ${created} created, ${updated} updated, ${unchanged} unchanged.`,
|
|
12844
|
+
{ created, updated, unchanged },
|
|
12845
|
+
[{ tool: "architecture", description: "View the map", parameters: { action: "show" } }]
|
|
13173
12846
|
)
|
|
13174
12847
|
};
|
|
13175
12848
|
}
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
)
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
12849
|
+
if (action === "check") {
|
|
12850
|
+
const projectRoot = resolveProjectRoot();
|
|
12851
|
+
if (!projectRoot) {
|
|
12852
|
+
return {
|
|
12853
|
+
content: [{
|
|
12854
|
+
type: "text",
|
|
12855
|
+
text: "# Scan Failed\n\nCannot find project root (looked for `convex/schema.ts` in cwd and parent). Set `WORKSPACE_PATH` env var to the absolute path of the Product OS project root."
|
|
12856
|
+
}],
|
|
12857
|
+
structuredContent: failure(
|
|
12858
|
+
"VALIDATION_ERROR",
|
|
12859
|
+
"Cannot find project root.",
|
|
12860
|
+
"Set WORKSPACE_PATH env var to the absolute path of the Product OS project root."
|
|
12861
|
+
)
|
|
12862
|
+
};
|
|
12863
|
+
}
|
|
12864
|
+
await ensureCollection();
|
|
12865
|
+
const all = await listArchEntries();
|
|
12866
|
+
const layers = byTag(all, "layer");
|
|
12867
|
+
const nodes = byTag(all, "node");
|
|
12868
|
+
const result = scanDependencies(projectRoot, layers, nodes);
|
|
12869
|
+
return {
|
|
12870
|
+
content: [{ type: "text", text: formatScanReport(result) }],
|
|
12871
|
+
structuredContent: success(
|
|
12872
|
+
result.violations.length === 0 ? `Health check passed: 0 violations across ${result.filesScanned} files.` : `Health check found ${result.violations.length} violation(s) across ${result.filesScanned} files.`,
|
|
12873
|
+
{
|
|
12874
|
+
violations: result.violations.length,
|
|
12875
|
+
filesScanned: result.filesScanned,
|
|
12876
|
+
importsChecked: result.importsChecked,
|
|
12877
|
+
unmappedImports: result.unmappedImports
|
|
12878
|
+
}
|
|
12879
|
+
)
|
|
12880
|
+
};
|
|
12881
|
+
}
|
|
12882
|
+
return unknownAction(action, ["seed", "check"]);
|
|
12883
|
+
})
|
|
12884
|
+
);
|
|
12885
|
+
trackWriteTool(archAdminTool);
|
|
12886
|
+
}
|
|
13198
12887
|
}
|
|
13199
12888
|
function scanDependencies(projectRoot, layers, nodes) {
|
|
13200
12889
|
const layerMap = /* @__PURE__ */ new Map();
|
|
@@ -13429,29 +13118,6 @@ async function markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapsho
|
|
|
13429
13118
|
}
|
|
13430
13119
|
}
|
|
13431
13120
|
}
|
|
13432
|
-
function extractSessionEntryIds(priorSessions) {
|
|
13433
|
-
const allSeen = /* @__PURE__ */ new Set();
|
|
13434
|
-
const all = [];
|
|
13435
|
-
let lastSessionOnly = [];
|
|
13436
|
-
for (let i = 0; i < priorSessions.length; i++) {
|
|
13437
|
-
const s = priorSessions[i];
|
|
13438
|
-
const created = Array.isArray(s.entriesCreated) ? s.entriesCreated : [];
|
|
13439
|
-
const modified = Array.isArray(s.entriesModified) ? s.entriesModified : [];
|
|
13440
|
-
const ids = [...created, ...modified].filter((id) => typeof id === "string" && id.length > 0);
|
|
13441
|
-
if (i === 0) {
|
|
13442
|
-
lastSessionOnly = [...new Set(ids)].slice(0, 5);
|
|
13443
|
-
}
|
|
13444
|
-
for (const id of ids) {
|
|
13445
|
-
if (!allSeen.has(id)) {
|
|
13446
|
-
allSeen.add(id);
|
|
13447
|
-
all.push(id);
|
|
13448
|
-
if (all.length >= 10) break;
|
|
13449
|
-
}
|
|
13450
|
-
}
|
|
13451
|
-
if (all.length >= 10) break;
|
|
13452
|
-
}
|
|
13453
|
-
return { all, lastSessionOnly };
|
|
13454
|
-
}
|
|
13455
13121
|
var VALID_TASK_DOMAINS = [
|
|
13456
13122
|
"auth",
|
|
13457
13123
|
"strategy",
|
|
@@ -13509,8 +13175,8 @@ function registerOrientTool(server) {
|
|
|
13509
13175
|
server.registerTool(
|
|
13510
13176
|
"orient",
|
|
13511
13177
|
{
|
|
13512
|
-
title: "Orient \u2014
|
|
13513
|
-
description: "
|
|
13178
|
+
title: "Orient \u2014 Task Grounding",
|
|
13179
|
+
description: "Task-grounded context loader. Use AFTER `start_pb` (the canonical session opener) to load governance and context for a specific task. Returns workspace context with a single recommended next action for low-readiness workspaces, or a standup-style briefing for established workspaces.\n\n**Not the session opener.** For 'how do I begin a session' or 'Start PB', call `start_pb` instead. `orient` is for refreshing or scoping context once a session is already underway.\n\nCompleting orientation unlocks write tools for the active session.\n\n**tier:** Controls payload depth. `standard` (default, ~256 KB) is the recommended default. `summary` (~10 KB) for quick mid-session re-orientation. `full` for complete payload when deep context is needed.\n\n**mode:** `full` (default) returns full context. `brief` returns compact summary \u2014 mapped to tier=summary internally. Prefer `tier` for explicit depth control.\n\n**task:** Optional natural-language task description. When provided, returns task-scoped context (scored, relevant entries) in addition to standard orient sections.\n\n**scope:** Optional domain scope. Filters governance entries to those relevant for the specified domain. Authority roots include strategy, product, product-design, engineering, architecture, data, governance, and go-to-market. Child scopes such as product-design/ux, engineering/frontend, data/analytics, and governance/principles are accepted. Legacy internal scopes remain accepted.",
|
|
13514
13180
|
inputSchema: orientSchema,
|
|
13515
13181
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
|
|
13516
13182
|
},
|
|
@@ -13547,29 +13213,22 @@ function registerOrientTool(server) {
|
|
|
13547
13213
|
}
|
|
13548
13214
|
let priorSessions = [];
|
|
13549
13215
|
let recoveryBlock = null;
|
|
13550
|
-
|
|
13551
|
-
try {
|
|
13552
|
-
const sessionsResult = await kernelQuery("agent.recentSessions", { limit: 3 });
|
|
13553
|
-
priorSessions = sessionsResult?.sessions ?? [];
|
|
13554
|
-
recoveryBlock = sessionsResult?.recoveryBlock ?? null;
|
|
13555
|
-
} catch {
|
|
13556
|
-
}
|
|
13557
|
-
}
|
|
13558
|
-
const { all: sessionEntryIds, lastSessionOnly } = extractSessionEntryIds(priorSessions);
|
|
13559
|
-
let orientEntries = null;
|
|
13216
|
+
let orientView = null;
|
|
13560
13217
|
try {
|
|
13561
13218
|
const orientArgs = {};
|
|
13562
13219
|
if (task) orientArgs.task = task;
|
|
13563
13220
|
if (scope) orientArgs.scope = scope;
|
|
13564
|
-
if (sessionEntryIds.length > 0) orientArgs.sessionEntryIds = sessionEntryIds;
|
|
13565
|
-
if (lastSessionOnly.length > 0) orientArgs.lastSessionEntryIds = lastSessionOnly;
|
|
13566
13221
|
orientArgs.tier = effectiveTier;
|
|
13567
|
-
|
|
13222
|
+
orientView = await kernelQuery("chain.getOrientView", orientArgs);
|
|
13568
13223
|
} catch {
|
|
13569
13224
|
}
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13225
|
+
if (orientView) {
|
|
13226
|
+
priorSessions = orientView.priorSessions ?? [];
|
|
13227
|
+
recoveryBlock = orientView.recoveryBlock ?? null;
|
|
13228
|
+
}
|
|
13229
|
+
const hasTaskGrounding = Boolean(task && orientView?.taskContext?.context?.length > 0);
|
|
13230
|
+
if (wsCtx && hasTaskGrounding && orientView?.writeBackHints) {
|
|
13231
|
+
const hints = Array.isArray(orientView.writeBackHints) ? orientView.writeBackHints : [];
|
|
13573
13232
|
if (hints.length > 0) {
|
|
13574
13233
|
trackWriteBackHintServed(wsCtx.workspaceId, {
|
|
13575
13234
|
hint_count: hints.length,
|
|
@@ -13620,9 +13279,9 @@ function registerOrientTool(server) {
|
|
|
13620
13279
|
const blankResult = {
|
|
13621
13280
|
content: [{ type: "text", text: scanLines.join("\n") }],
|
|
13622
13281
|
structuredContent: success(
|
|
13623
|
-
"Workspace is blank. Use the
|
|
13624
|
-
{ stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "Use the `
|
|
13625
|
-
[{ tool: "
|
|
13282
|
+
"Workspace is blank. Use the start_pb tool for guided setup.",
|
|
13283
|
+
{ stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "Use the `start_pb` tool for the full guided setup experience." },
|
|
13284
|
+
[{ tool: "start_pb", description: "Guided workspace setup", parameters: {} }]
|
|
13626
13285
|
)
|
|
13627
13286
|
};
|
|
13628
13287
|
reportOrientWrapperBytes(blankResult, false);
|
|
@@ -13642,8 +13301,8 @@ function registerOrientTool(server) {
|
|
|
13642
13301
|
if (mode === "brief") {
|
|
13643
13302
|
const briefStage = readiness?.stage ?? (readiness?.score != null ? readiness.score < 50 ? "seeded" : "grounded" : "unknown");
|
|
13644
13303
|
lines.push(`Brain stage: ${briefStage}`);
|
|
13645
|
-
if (
|
|
13646
|
-
const sc =
|
|
13304
|
+
if (orientView?.strategicContext) {
|
|
13305
|
+
const sc = orientView.strategicContext;
|
|
13647
13306
|
if (sc.vision) lines.push(`Vision: ${sc.vision}`);
|
|
13648
13307
|
if (sc.purpose) lines.push(`Purpose: ${sc.purpose}`);
|
|
13649
13308
|
if (sc.productAreaCount != null && sc.productAreaCount > 0) {
|
|
@@ -13654,11 +13313,11 @@ function registerOrientTool(server) {
|
|
|
13654
13313
|
}
|
|
13655
13314
|
lines.push(`${sc.activeBetCount} active bet(s), ${sc.activeTensionCount} tension(s).`);
|
|
13656
13315
|
}
|
|
13657
|
-
if (
|
|
13658
|
-
lines.push(`Task context: ${
|
|
13316
|
+
if (orientView?.taskContext && orientView.taskContext.context.length > 0) {
|
|
13317
|
+
lines.push(`Task context: ${orientView.taskContext.totalFound} relevant entries (${orientView.taskContext.confidence} confidence).`);
|
|
13659
13318
|
}
|
|
13660
|
-
if (
|
|
13661
|
-
const retrieval =
|
|
13319
|
+
if (orientView?.startup?.domainRetrieval) {
|
|
13320
|
+
const retrieval = orientView.startup.domainRetrieval;
|
|
13662
13321
|
const scopedTo = retrieval.resolvedDomainSlug ? ` (${retrieval.resolvedDomainSlug})` : "";
|
|
13663
13322
|
lines.push(`Domain retrieval: ${retrieval.state}${scopedTo}`);
|
|
13664
13323
|
if (retrieval.activeSource) lines.push(`Active source: ${retrieval.activeSource}`);
|
|
@@ -13669,29 +13328,29 @@ function registerOrientTool(server) {
|
|
|
13669
13328
|
lines.push(`Domain relation evidence: ${retrieval.directRatifiedCount ?? 0} direct, ${retrieval.inheritedRatifiedCount ?? 0} inherited, ${retrieval.orgFallbackCount ?? 0} org fallback`);
|
|
13670
13329
|
}
|
|
13671
13330
|
}
|
|
13672
|
-
if (
|
|
13331
|
+
if (orientView?.writeBackHints && orientView.writeBackHints.length > 0) {
|
|
13673
13332
|
lines.push("");
|
|
13674
13333
|
lines.push("Write-back hints (what to capture this session):");
|
|
13675
|
-
for (const h of
|
|
13334
|
+
for (const h of orientView.writeBackHints) {
|
|
13676
13335
|
lines.push(` ${h.collectionSlug}: ${h.hint}`);
|
|
13677
13336
|
}
|
|
13678
13337
|
}
|
|
13679
|
-
if (
|
|
13680
|
-
for (const e of
|
|
13338
|
+
if (orientView?.activeBets?.length > 0) {
|
|
13339
|
+
for (const e of orientView.activeBets) {
|
|
13681
13340
|
const tensions = e.linkedTensions;
|
|
13682
13341
|
const tensionPart = tensions?.length ? ` \u2014 ${tensions.map((t) => `${t.entryId ?? t.name} (${t.severity ?? "\u2014"})`).join(", ")}` : "";
|
|
13683
13342
|
const originPart = e.origin ? ` (origin: ${e.origin})` : "";
|
|
13684
13343
|
lines.push(`- \`${e.entryId ?? e._id}\` ${e.name}${originPart}${tensionPart}`);
|
|
13685
13344
|
}
|
|
13686
13345
|
}
|
|
13687
|
-
if (
|
|
13688
|
-
const tm =
|
|
13346
|
+
if (orientView?.trustMetrics) {
|
|
13347
|
+
const tm = orientView.trustMetrics;
|
|
13689
13348
|
if (tm.unverified > 0 || tm.verified > 0) {
|
|
13690
13349
|
const capNote = tm.scannedCap ? "+" : "";
|
|
13691
13350
|
lines.push(`Trust: ${tm.verified} verified, ${tm.unverified} unverified, ${tm.noStatus} no-status (${tm.total}${capNote} scanned).`);
|
|
13692
13351
|
}
|
|
13693
13352
|
}
|
|
13694
|
-
const briefWna = formatWhatNeedsAttentionBrief(
|
|
13353
|
+
const briefWna = formatWhatNeedsAttentionBrief(orientView?.whatNeedsAttention);
|
|
13695
13354
|
if (briefWna.length > 0) {
|
|
13696
13355
|
lines.push("");
|
|
13697
13356
|
lines.push(...briefWna);
|
|
@@ -13728,7 +13387,7 @@ function registerOrientTool(server) {
|
|
|
13728
13387
|
lines.push(...docCompleteness.lines);
|
|
13729
13388
|
}
|
|
13730
13389
|
}
|
|
13731
|
-
if (
|
|
13390
|
+
if (orientView) {
|
|
13732
13391
|
lines.push("");
|
|
13733
13392
|
if (task) {
|
|
13734
13393
|
const mapGovernanceEntry = (e) => ({
|
|
@@ -13737,10 +13396,11 @@ function registerOrientTool(server) {
|
|
|
13737
13396
|
description: typeof e.preview === "string" ? e.preview : void 0,
|
|
13738
13397
|
tier: typeof e.tier === "number" ? e.tier : void 0
|
|
13739
13398
|
});
|
|
13399
|
+
const gov = orientView.governance ?? { principles: [], standards: [], businessRules: [] };
|
|
13740
13400
|
lines.push(...buildOperatingProtocol({
|
|
13741
|
-
principles: (
|
|
13742
|
-
standards: (
|
|
13743
|
-
businessRules: (
|
|
13401
|
+
principles: (gov.principles ?? []).map(mapGovernanceEntry),
|
|
13402
|
+
standards: (gov.standards ?? []).map(mapGovernanceEntry),
|
|
13403
|
+
businessRules: (gov.businessRules ?? []).map(mapGovernanceEntry)
|
|
13744
13404
|
}, task));
|
|
13745
13405
|
} else {
|
|
13746
13406
|
lines.push(...buildOperatingProtocol());
|
|
@@ -13766,9 +13426,9 @@ function registerOrientTool(server) {
|
|
|
13766
13426
|
lines.push("---");
|
|
13767
13427
|
lines.push("_No active agent session. Call `session action=start` to begin._");
|
|
13768
13428
|
}
|
|
13769
|
-
const briefTruncated =
|
|
13429
|
+
const briefTruncated = orientView?._budget?.truncated ?? orientView?._truncated ?? false;
|
|
13770
13430
|
if (briefTruncated) {
|
|
13771
|
-
const reasons =
|
|
13431
|
+
const reasons = orientView?._budget?.truncationReasons;
|
|
13772
13432
|
lines.push("");
|
|
13773
13433
|
if (reasons && reasons.length > 0) {
|
|
13774
13434
|
lines.push(`_Context truncated to fit tier budget: ${reasons.join(", ")}. Use tier=full for complete payload._`);
|
|
@@ -13793,7 +13453,7 @@ function registerOrientTool(server) {
|
|
|
13793
13453
|
taskGroundingRequired: !hasTaskGrounding,
|
|
13794
13454
|
orientationStatus: orientationStatus2,
|
|
13795
13455
|
nextStep: hasTaskGrounding ? void 0 : 'Run `orient task="describe the work"` before substantive work.',
|
|
13796
|
-
...
|
|
13456
|
+
...orientView?._budget ? { _budget: orientView._budget } : {}
|
|
13797
13457
|
}
|
|
13798
13458
|
)
|
|
13799
13459
|
};
|
|
@@ -13852,8 +13512,8 @@ function registerOrientTool(server) {
|
|
|
13852
13512
|
if (task) {
|
|
13853
13513
|
lines.push(`**Brain stage: ${orientStage}.** Working on: **${task}**`);
|
|
13854
13514
|
lines.push("");
|
|
13855
|
-
if (
|
|
13856
|
-
const sc =
|
|
13515
|
+
if (orientView?.strategicContext) {
|
|
13516
|
+
const sc = orientView.strategicContext;
|
|
13857
13517
|
lines.push("## Strategic Context");
|
|
13858
13518
|
if (sc.vision) lines.push(`**Vision:** ${sc.vision}`);
|
|
13859
13519
|
if (sc.purpose) lines.push(`**Purpose:** ${sc.purpose}`);
|
|
@@ -13867,11 +13527,11 @@ function registerOrientTool(server) {
|
|
|
13867
13527
|
lines.push(`${betLine} ${sc.activeTensionCount} open tension(s).`);
|
|
13868
13528
|
lines.push("");
|
|
13869
13529
|
}
|
|
13870
|
-
if (
|
|
13530
|
+
if (orientView?.continuingFrom && orientView.continuingFrom.length > 0) {
|
|
13871
13531
|
lines.push("## Continuing from");
|
|
13872
13532
|
lines.push("_Prior-session entries most relevant to your task._");
|
|
13873
13533
|
lines.push("");
|
|
13874
|
-
for (const e of
|
|
13534
|
+
for (const e of orientView.continuingFrom) {
|
|
13875
13535
|
const id = e.entryId ?? e.name;
|
|
13876
13536
|
const type = e.canonicalKey ?? "generic";
|
|
13877
13537
|
const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
|
|
@@ -13890,11 +13550,11 @@ function registerOrientTool(server) {
|
|
|
13890
13550
|
}
|
|
13891
13551
|
lines.push("");
|
|
13892
13552
|
}
|
|
13893
|
-
if (
|
|
13553
|
+
if (orientView?.lastSessionTouched && orientView.lastSessionTouched.length > 0) {
|
|
13894
13554
|
lines.push("## Last session touched");
|
|
13895
13555
|
lines.push("_Entries created or modified in your most recent session._");
|
|
13896
13556
|
lines.push("");
|
|
13897
|
-
for (const e of
|
|
13557
|
+
for (const e of orientView.lastSessionTouched) {
|
|
13898
13558
|
const id = e.entryId ?? e.name;
|
|
13899
13559
|
const type = e.canonicalKey ?? "generic";
|
|
13900
13560
|
const coll = e.collectionSlug ? ` [${e.collectionSlug}]` : "";
|
|
@@ -13912,11 +13572,11 @@ function registerOrientTool(server) {
|
|
|
13912
13572
|
}
|
|
13913
13573
|
lines.push("");
|
|
13914
13574
|
}
|
|
13915
|
-
if (
|
|
13916
|
-
const tc =
|
|
13575
|
+
if (orientView?.taskContext && orientView.taskContext.context.length > 0) {
|
|
13576
|
+
const tc = orientView.taskContext;
|
|
13917
13577
|
lines.push("## Task Context");
|
|
13918
|
-
if (
|
|
13919
|
-
const retrieval =
|
|
13578
|
+
if (orientView.startup?.domainRetrieval) {
|
|
13579
|
+
const retrieval = orientView.startup.domainRetrieval;
|
|
13920
13580
|
const scopedTo = retrieval.resolvedDomainSlug ? ` (${retrieval.resolvedDomainSlug})` : "";
|
|
13921
13581
|
lines.push(`_Domain retrieval: ${retrieval.state}${scopedTo}_`);
|
|
13922
13582
|
if (retrieval.activeSource) lines.push(`_Active source: ${retrieval.activeSource}_`);
|
|
@@ -13936,34 +13596,34 @@ function registerOrientTool(server) {
|
|
|
13936
13596
|
}
|
|
13937
13597
|
lines.push("");
|
|
13938
13598
|
}
|
|
13939
|
-
if (
|
|
13599
|
+
if (orientView?.taskContext?.constellationEntries && orientView.taskContext.constellationEntries.length > 0) {
|
|
13940
13600
|
lines.push(...formatBetConstellationLines(
|
|
13941
|
-
|
|
13942
|
-
|
|
13601
|
+
orientView.taskContext.constellationEntries,
|
|
13602
|
+
orientView.taskContext.constellationBetName
|
|
13943
13603
|
));
|
|
13944
13604
|
}
|
|
13945
|
-
if (
|
|
13605
|
+
if (orientView?.writeBackHints && orientView.writeBackHints.length > 0) {
|
|
13946
13606
|
lines.push("");
|
|
13947
13607
|
lines.push("## Write-Back Hints");
|
|
13948
13608
|
lines.push("_What to capture this session (derived from task context):_");
|
|
13949
13609
|
lines.push("");
|
|
13950
|
-
for (const h of
|
|
13610
|
+
for (const h of orientView.writeBackHints) {
|
|
13951
13611
|
lines.push(`- **${h.collectionSlug}**: ${h.hint}`);
|
|
13952
13612
|
}
|
|
13953
13613
|
lines.push("");
|
|
13954
13614
|
}
|
|
13955
|
-
if (
|
|
13615
|
+
if (orientView) {
|
|
13956
13616
|
const result = await runAlignmentCheck(
|
|
13957
13617
|
task,
|
|
13958
|
-
|
|
13959
|
-
|
|
13618
|
+
orientView.activeBets ?? [],
|
|
13619
|
+
orientView.taskContext?.context
|
|
13960
13620
|
);
|
|
13961
13621
|
lines.push(...buildAlignmentCheckLines(result));
|
|
13962
|
-
lines.push(...formatInitiativeStatusLines(
|
|
13963
|
-
lines.push(...formatWorkstreamHealthLines(
|
|
13964
|
-
lines.push(...formatWhatNeedsAttentionLines(
|
|
13965
|
-
if (
|
|
13966
|
-
const tm =
|
|
13622
|
+
lines.push(...formatInitiativeStatusLines(orientView.initiativeStatus));
|
|
13623
|
+
lines.push(...formatWorkstreamHealthLines(orientView.workstreamHealth));
|
|
13624
|
+
lines.push(...formatWhatNeedsAttentionLines(orientView.whatNeedsAttention));
|
|
13625
|
+
if (orientView.trustMetrics) {
|
|
13626
|
+
const tm = orientView.trustMetrics;
|
|
13967
13627
|
if (tm.verified > 0 || tm.unverified > 0) {
|
|
13968
13628
|
const capNote = tm.scannedCap ? " (capped at 500)" : "";
|
|
13969
13629
|
lines.push("## Trust metrics");
|
|
@@ -13986,12 +13646,12 @@ function registerOrientTool(server) {
|
|
|
13986
13646
|
lines.push(...docCompleteness.lines);
|
|
13987
13647
|
}
|
|
13988
13648
|
}
|
|
13989
|
-
if (
|
|
13990
|
-
if (
|
|
13649
|
+
if (orientView) {
|
|
13650
|
+
if (orientView.activeBets?.length > 0) {
|
|
13991
13651
|
lines.push("## Active bets \u2014 current scope");
|
|
13992
13652
|
lines.push("_Work outside these bets requires explicit user confirmation._");
|
|
13993
13653
|
lines.push("");
|
|
13994
|
-
for (const e of
|
|
13654
|
+
for (const e of orientView.activeBets) {
|
|
13995
13655
|
lines.push(fmt(e));
|
|
13996
13656
|
const tensions = e.linkedTensions;
|
|
13997
13657
|
if (tensions?.length) {
|
|
@@ -14004,44 +13664,44 @@ function registerOrientTool(server) {
|
|
|
14004
13664
|
}
|
|
14005
13665
|
lines.push("");
|
|
14006
13666
|
}
|
|
14007
|
-
if (
|
|
13667
|
+
if (orientView.activeGoals?.length > 0) {
|
|
14008
13668
|
lines.push("## Active goals");
|
|
14009
|
-
|
|
13669
|
+
orientView.activeGoals.forEach((e) => lines.push(fmt(e)));
|
|
14010
13670
|
lines.push("");
|
|
14011
13671
|
}
|
|
14012
|
-
if (
|
|
13672
|
+
if (orientView.strategyHighlights?.length > 0) {
|
|
14013
13673
|
lines.push("## Strategy highlights");
|
|
14014
13674
|
lines.push("_One-sentence strategy, positioning, moat, business model, GTM \u2014 high-level strategic context._");
|
|
14015
13675
|
lines.push("");
|
|
14016
|
-
|
|
13676
|
+
orientView.strategyHighlights.forEach((e) => lines.push(fmt(e)));
|
|
14017
13677
|
lines.push("");
|
|
14018
13678
|
}
|
|
14019
|
-
if (
|
|
13679
|
+
if (orientView.playingField?.length > 0) {
|
|
14020
13680
|
lines.push("## Playing field");
|
|
14021
13681
|
lines.push("_Products and opportunities in the strategic landscape._");
|
|
14022
13682
|
lines.push("");
|
|
14023
|
-
|
|
13683
|
+
orientView.playingField.forEach((e) => lines.push(fmt(e)));
|
|
14024
13684
|
lines.push("");
|
|
14025
13685
|
}
|
|
14026
|
-
if (
|
|
13686
|
+
if (orientView.recentDecisions?.length > 0) {
|
|
14027
13687
|
lines.push("## Recent decisions");
|
|
14028
|
-
|
|
13688
|
+
orientView.recentDecisions.forEach((e) => lines.push(fmt(e)));
|
|
14029
13689
|
lines.push("");
|
|
14030
13690
|
}
|
|
14031
|
-
if (
|
|
13691
|
+
if (orientView.recentlySuperseded?.length > 0) {
|
|
14032
13692
|
lines.push("## Recently superseded");
|
|
14033
|
-
|
|
13693
|
+
orientView.recentlySuperseded.forEach((e) => lines.push(fmt(e)));
|
|
14034
13694
|
lines.push("");
|
|
14035
13695
|
}
|
|
14036
|
-
if (
|
|
13696
|
+
if (orientView.staleEntries?.length > 0) {
|
|
14037
13697
|
lines.push("## Needs confirmation");
|
|
14038
|
-
lines.push(`_Domain stratum entries not confirmed in ${
|
|
14039
|
-
|
|
13698
|
+
lines.push(`_Domain stratum entries not confirmed in ${orientView.stalenessThresholdDays} days._`);
|
|
13699
|
+
orientView.staleEntries.forEach((e) => lines.push(fmt(e)));
|
|
14040
13700
|
lines.push("");
|
|
14041
13701
|
}
|
|
14042
|
-
if (
|
|
13702
|
+
if (orientView.architectureNotes?.length > 0) {
|
|
14043
13703
|
lines.push("## Architecture notes");
|
|
14044
|
-
|
|
13704
|
+
orientView.architectureNotes.forEach((e) => lines.push(fmt(e)));
|
|
14045
13705
|
lines.push("");
|
|
14046
13706
|
}
|
|
14047
13707
|
const mapGovernanceEntry = (e) => ({
|
|
@@ -14050,10 +13710,11 @@ function registerOrientTool(server) {
|
|
|
14050
13710
|
description: typeof e.preview === "string" ? e.preview : void 0,
|
|
14051
13711
|
tier: typeof e.tier === "number" ? e.tier : void 0
|
|
14052
13712
|
});
|
|
13713
|
+
const gov = orientView.governance ?? { principles: [], standards: [], businessRules: [] };
|
|
14053
13714
|
lines.push(...buildOperatingProtocol({
|
|
14054
|
-
principles: (
|
|
14055
|
-
standards: (
|
|
14056
|
-
businessRules: (
|
|
13715
|
+
principles: (gov.principles ?? []).map(mapGovernanceEntry),
|
|
13716
|
+
standards: (gov.standards ?? []).map(mapGovernanceEntry),
|
|
13717
|
+
businessRules: (gov.businessRules ?? []).map(mapGovernanceEntry)
|
|
14057
13718
|
}, task));
|
|
14058
13719
|
}
|
|
14059
13720
|
let allEntries = [];
|
|
@@ -14070,20 +13731,20 @@ function registerOrientTool(server) {
|
|
|
14070
13731
|
} else {
|
|
14071
13732
|
lines.push(`**Brain stage: ${orientStage}.**`);
|
|
14072
13733
|
lines.push("");
|
|
14073
|
-
if (
|
|
13734
|
+
if (orientView?.activeBets?.length) {
|
|
14074
13735
|
lines.push("## Active bets \u2014 current scope");
|
|
14075
13736
|
lines.push("_Work outside these bets requires explicit user confirmation._");
|
|
14076
13737
|
lines.push("");
|
|
14077
|
-
for (const e of
|
|
13738
|
+
for (const e of orientView.activeBets) {
|
|
14078
13739
|
lines.push(fmt(e));
|
|
14079
13740
|
}
|
|
14080
13741
|
lines.push("");
|
|
14081
13742
|
}
|
|
14082
|
-
lines.push(...formatInitiativeStatusLines(
|
|
14083
|
-
lines.push(...formatWorkstreamHealthLines(
|
|
14084
|
-
lines.push(...formatWhatNeedsAttentionLines(
|
|
14085
|
-
if (
|
|
14086
|
-
const tm =
|
|
13743
|
+
lines.push(...formatInitiativeStatusLines(orientView?.initiativeStatus));
|
|
13744
|
+
lines.push(...formatWorkstreamHealthLines(orientView?.workstreamHealth));
|
|
13745
|
+
lines.push(...formatWhatNeedsAttentionLines(orientView?.whatNeedsAttention));
|
|
13746
|
+
if (orientView?.trustMetrics) {
|
|
13747
|
+
const tm = orientView.trustMetrics;
|
|
14087
13748
|
if (tm.verified > 0 || tm.unverified > 0) {
|
|
14088
13749
|
const capNote = tm.scannedCap ? " (capped at 500)" : "";
|
|
14089
13750
|
lines.push("## Trust metrics");
|
|
@@ -14166,9 +13827,9 @@ function registerOrientTool(server) {
|
|
|
14166
13827
|
lines.push("---");
|
|
14167
13828
|
lines.push("_No active agent session. Call `session action=start` to begin a tracked session._");
|
|
14168
13829
|
}
|
|
14169
|
-
const fullTruncated =
|
|
13830
|
+
const fullTruncated = orientView?._budget?.truncated ?? orientView?._truncated ?? false;
|
|
14170
13831
|
if (fullTruncated) {
|
|
14171
|
-
const reasons =
|
|
13832
|
+
const reasons = orientView?._budget?.truncationReasons;
|
|
14172
13833
|
lines.push("");
|
|
14173
13834
|
if (reasons && reasons.length > 0) {
|
|
14174
13835
|
lines.push(`_Context truncated to fit tier budget: ${reasons.join(", ")}. Use tier=full for complete payload._`);
|
|
@@ -14187,10 +13848,10 @@ function registerOrientTool(server) {
|
|
|
14187
13848
|
sessionId: agentSessionId,
|
|
14188
13849
|
taskGroundingStatus: hasTaskGrounding ? "task_scoped" : task ? "missing_task_context" : "workspace_only",
|
|
14189
13850
|
taskGroundingRequired: !hasTaskGrounding,
|
|
14190
|
-
domainRetrieval:
|
|
13851
|
+
domainRetrieval: orientView?.startup?.domainRetrieval,
|
|
14191
13852
|
orientationStatus,
|
|
14192
13853
|
nextStep: hasTaskGrounding ? void 0 : 'Run `orient task="describe the work"` before substantive work.',
|
|
14193
|
-
...
|
|
13854
|
+
...orientView?._budget ? { _budget: orientView._budget } : {}
|
|
14194
13855
|
}
|
|
14195
13856
|
)
|
|
14196
13857
|
};
|
|
@@ -14609,7 +14270,7 @@ var ALL_TOOL_SCHEMAS = [
|
|
|
14609
14270
|
{ name: "update-entry", schema: updateEntrySchema },
|
|
14610
14271
|
{ name: "get-history", schema: getHistorySchema },
|
|
14611
14272
|
{ name: "commit-entry", schema: commitEntrySchema },
|
|
14612
|
-
{ name: "
|
|
14273
|
+
{ name: "start_pb", schema: startPbSchema },
|
|
14613
14274
|
{ name: "get-usage-summary", schema: usageSummarySchema },
|
|
14614
14275
|
{ name: "chain", schema: chainSchema },
|
|
14615
14276
|
{ name: "chain-version", schema: chainVersionSchema },
|
|
@@ -14703,15 +14364,61 @@ function registerHealthTools(server) {
|
|
|
14703
14364
|
);
|
|
14704
14365
|
}
|
|
14705
14366
|
|
|
14706
|
-
// src/tools/
|
|
14367
|
+
// src/tools/record_activation.ts
|
|
14707
14368
|
import { z as z24 } from "zod";
|
|
14369
|
+
var recordActivationSchema = z24.object({
|
|
14370
|
+
confirmedEntryCount: z24.number().int().min(0).describe(
|
|
14371
|
+
"Confirmed-entry count from the Phase 4 capture loop. The mutation enforces >=10 (DEC-994)."
|
|
14372
|
+
),
|
|
14373
|
+
entriesAcrossCollections: z24.number().int().min(0).describe(
|
|
14374
|
+
"Number of distinct collections those entries span. The mutation enforces >=2 (DEC-994 diversity soft-gate)."
|
|
14375
|
+
),
|
|
14376
|
+
retrievalDemoConfirmed: z24.boolean().describe(
|
|
14377
|
+
"True if the BR-141 retrieval round-trip ran successfully in Phase 4. The mutation rejects false."
|
|
14378
|
+
)
|
|
14379
|
+
});
|
|
14380
|
+
function registerRecordActivationTools(server) {
|
|
14381
|
+
server.registerTool(
|
|
14382
|
+
"record_activation",
|
|
14383
|
+
{
|
|
14384
|
+
title: "Record activation receipt \u2014 chat-only",
|
|
14385
|
+
description: "Writes the per-user activation receipt that closes the WP-431 install-to-activation arc for chat-only surfaces (Cursor Desktop, Claude Desktop, ChatGPT). The skill body's Phase 5 calls this AFTER Phase 4 gates pass: confirmedEntryCount>=10, entriesAcrossCollections>=2, retrievalDemoConfirmed=true. surfaceCapability is hardcoded to 'chat-only' \u2014 this tool is NOT for CLI agents (CLI uses the gateway directly). Idempotent: a re-run after activation returns alreadyActivated=true.",
|
|
14386
|
+
inputSchema: recordActivationSchema,
|
|
14387
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, destructiveHint: false }
|
|
14388
|
+
},
|
|
14389
|
+
thinWrapper(async ({ confirmedEntryCount, entriesAcrossCollections, retrievalDemoConfirmed }) => {
|
|
14390
|
+
const mutationResult = await kernelMutation(
|
|
14391
|
+
"setup.recordActivationReceipt",
|
|
14392
|
+
{
|
|
14393
|
+
surfaceCapability: "chat-only",
|
|
14394
|
+
confirmedEntryCount,
|
|
14395
|
+
entriesAcrossCollections,
|
|
14396
|
+
retrievalDemoConfirmed
|
|
14397
|
+
}
|
|
14398
|
+
);
|
|
14399
|
+
const result = {
|
|
14400
|
+
activated: true,
|
|
14401
|
+
alreadyActivated: mutationResult.alreadyActivated,
|
|
14402
|
+
receiptId: mutationResult.receiptId ?? null
|
|
14403
|
+
};
|
|
14404
|
+
const summary = mutationResult.alreadyActivated ? "Activation receipt already recorded \u2014 chat-only flow short-circuits." : "Activation recorded \u2014 chat-only flow complete.";
|
|
14405
|
+
return {
|
|
14406
|
+
content: textContent(summary),
|
|
14407
|
+
structuredContent: success(summary, result)
|
|
14408
|
+
};
|
|
14409
|
+
})
|
|
14410
|
+
);
|
|
14411
|
+
}
|
|
14412
|
+
|
|
14413
|
+
// src/tools/audit.ts
|
|
14414
|
+
import { z as z25 } from "zod";
|
|
14708
14415
|
var AUDIT_ACTIONS = ["run"];
|
|
14709
|
-
var auditSchema =
|
|
14710
|
-
action:
|
|
14416
|
+
var auditSchema = z25.object({
|
|
14417
|
+
action: z25.enum(AUDIT_ACTIONS).describe(
|
|
14711
14418
|
"'run': run the STD-113 hygiene audit for a bet entry."
|
|
14712
14419
|
),
|
|
14713
|
-
entryId:
|
|
14714
|
-
phase:
|
|
14420
|
+
entryId: z25.string().describe("Bet entry ID to audit, e.g. 'BET-182'"),
|
|
14421
|
+
phase: z25.enum(["shaping", "handoff"]).default("shaping").optional().describe(
|
|
14715
14422
|
"'shaping': check shaping-phase fields only. 'handoff': check all required fields including buildContract/buildSequence/exclusions/risks. Default: shaping."
|
|
14716
14423
|
)
|
|
14717
14424
|
});
|
|
@@ -14720,7 +14427,7 @@ function registerAuditTools(server) {
|
|
|
14720
14427
|
"audit",
|
|
14721
14428
|
{
|
|
14722
14429
|
title: "Audit",
|
|
14723
|
-
description: "Run STD-113 hygiene audit on a bet entry. Checks 13 gates covering:\n\n- **Relations**: intentional links, no auto-link noise, strategic anchoring\n- **Risk/Tension**: risks in correct collection, tensions linked\n- **Field completeness**: mandatory fields populated for current phase\n- **Elements**: correct collection, committed status\n- **Acceptance criteria**: doneWhen populated and verifiable\n- **Structural**: no circular relations, appetite consistency\n\nUse `phase=shaping` (default) before starting implementation. Use `phase=handoff` before handing off to AI Engineer.",
|
|
14430
|
+
description: "Run STD-113 hygiene audit on a bet entry. Checks 13 gates (shaping) / 18 gates (handoff) covering:\n\n- **Relations**: intentional links, no auto-link noise, strategic anchoring\n- **Risk/Tension**: risks in correct collection, tensions linked\n- **Field completeness**: mandatory fields populated for current phase\n- **Elements**: correct collection, committed status\n- **Acceptance criteria**: doneWhen populated and verifiable\n- **Structural**: no circular relations, appetite consistency\n\nUse `phase=shaping` (default) before starting implementation. Use `phase=handoff` before handing off to AI Engineer.",
|
|
14724
14431
|
inputSchema: auditSchema,
|
|
14725
14432
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
14726
14433
|
},
|
|
@@ -14822,13 +14529,13 @@ async function handleAuditRun(entryId, phase) {
|
|
|
14822
14529
|
}
|
|
14823
14530
|
|
|
14824
14531
|
// src/tools/governance.ts
|
|
14825
|
-
import { z as
|
|
14826
|
-
var governanceSchema =
|
|
14827
|
-
action:
|
|
14828
|
-
proposalId:
|
|
14829
|
-
verdict:
|
|
14830
|
-
reason:
|
|
14831
|
-
status:
|
|
14532
|
+
import { z as z26 } from "zod";
|
|
14533
|
+
var governanceSchema = z26.object({
|
|
14534
|
+
action: z26.enum(["list", "respond", "count"]).describe("Action: list open proposals, respond to a proposal, or count open proposals"),
|
|
14535
|
+
proposalId: z26.string().optional().describe("Proposal ID (required for respond action)"),
|
|
14536
|
+
verdict: z26.enum(["approve", "reject"]).optional().describe("Verdict for respond action: approve or reject"),
|
|
14537
|
+
reason: z26.string().optional().describe("Reason for the verdict (required when rejecting)"),
|
|
14538
|
+
status: z26.enum(["open", "approved", "objected", "expired"]).optional().describe("Filter proposals by status (default: open). Only used with list action.")
|
|
14832
14539
|
});
|
|
14833
14540
|
function registerGovernanceTools(server) {
|
|
14834
14541
|
const tool = server.registerTool(
|
|
@@ -14954,16 +14661,16 @@ function registerGovernanceTools(server) {
|
|
|
14954
14661
|
}
|
|
14955
14662
|
|
|
14956
14663
|
// src/tools/documents.ts
|
|
14957
|
-
import { z as
|
|
14664
|
+
import { z as z27 } from "zod";
|
|
14958
14665
|
var DOCUMENTS_ACTIONS = ["get-last-verified-brief"];
|
|
14959
|
-
var documentsSchema =
|
|
14960
|
-
action:
|
|
14666
|
+
var documentsSchema = z27.object({
|
|
14667
|
+
action: z27.enum(DOCUMENTS_ACTIONS).describe(
|
|
14961
14668
|
"'get-last-verified-brief': fetch the most recent verified brief snapshot for a (templateId, scopeKey) pair. Returns the verified summary so agents can build delta narratives ('since you last verified, X workstreams advanced')."
|
|
14962
14669
|
),
|
|
14963
|
-
templateId:
|
|
14670
|
+
templateId: z27.string().describe(
|
|
14964
14671
|
"Brief template identifier \u2014 currently 'steering-brief' is the only registered template."
|
|
14965
14672
|
),
|
|
14966
|
-
scopeKey:
|
|
14673
|
+
scopeKey: z27.string().describe(
|
|
14967
14674
|
"Canonical scope key. Use 'workspace:<workspaceId>' for the full workspace brief, or 'initiative:<INI-ID>' for an initiative-scoped brief. The same formula is applied at write time (chainwork/docKernel/scopeKey.ts), so passing the wrong shape returns exists:false."
|
|
14968
14675
|
)
|
|
14969
14676
|
});
|
|
@@ -15561,12 +15268,12 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
|
|
|
15561
15268
|
}
|
|
15562
15269
|
|
|
15563
15270
|
// src/prompts/index.ts
|
|
15564
|
-
import { z as
|
|
15271
|
+
import { z as z28 } from "zod";
|
|
15565
15272
|
function registerPrompts(server) {
|
|
15566
15273
|
server.prompt(
|
|
15567
15274
|
"review-against-rules",
|
|
15568
15275
|
"Review code or a design decision against all business rules for a given domain. Fetches the rules and asks you to do a structured compliance review.",
|
|
15569
|
-
{ domain:
|
|
15276
|
+
{ domain: z28.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
|
|
15570
15277
|
async ({ domain }) => {
|
|
15571
15278
|
const entries = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
15572
15279
|
const rules = entries.filter((e) => e.data?.domain === domain);
|
|
@@ -15619,7 +15326,7 @@ Provide a structured review with a compliance status for each rule (COMPLIANT /
|
|
|
15619
15326
|
server.prompt(
|
|
15620
15327
|
"name-check",
|
|
15621
15328
|
"Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
|
|
15622
|
-
{ names:
|
|
15329
|
+
{ names: z28.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
|
|
15623
15330
|
async ({ names }) => {
|
|
15624
15331
|
const terms = await kernelQuery("chain.listEntries", { collectionSlug: "glossary" });
|
|
15625
15332
|
const glossaryContext = terms.map(
|
|
@@ -15655,7 +15362,7 @@ Format as a table: Name | Status | Canonical Form | Action Needed`
|
|
|
15655
15362
|
server.prompt(
|
|
15656
15363
|
"draft-decision-record",
|
|
15657
15364
|
"Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
|
|
15658
|
-
{ context:
|
|
15365
|
+
{ context: z28.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
|
|
15659
15366
|
async ({ context }) => {
|
|
15660
15367
|
const recentDecisions = await kernelQuery("chain.listEntries", { collectionSlug: "decisions" });
|
|
15661
15368
|
const sorted = [...recentDecisions].sort((a, b) => (b.data?.date ?? "") > (a.data?.date ?? "") ? 1 : -1).slice(0, 5);
|
|
@@ -15693,8 +15400,8 @@ After drafting, I can log it using the capture tool with collection "decisions".
|
|
|
15693
15400
|
"draft-rule-from-context",
|
|
15694
15401
|
"Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
|
|
15695
15402
|
{
|
|
15696
|
-
observation:
|
|
15697
|
-
domain:
|
|
15403
|
+
observation: z28.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
|
|
15404
|
+
domain: z28.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
|
|
15698
15405
|
},
|
|
15699
15406
|
async ({ observation, domain }) => {
|
|
15700
15407
|
const allRules = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
@@ -15772,7 +15479,7 @@ var INSTRUCTIONS = [
|
|
|
15772
15479
|
"",
|
|
15773
15480
|
"## Workflow",
|
|
15774
15481
|
"",
|
|
15775
|
-
" 1. Start: call `
|
|
15482
|
+
" 1. Start: call `start_pb` to begin \u2014 it starts a session automatically, detects your workspace stage, and returns the right guidance. Fresh workspaces get the pb-setup skill body; active workspaces get a standup briefing.",
|
|
15776
15483
|
" 2. Re-orient: call `orient` mid-session for task-scoped context or a compact status refresh.",
|
|
15777
15484
|
" 3. Discover: use `entries action=search` to find entries, or `entries action=list` to browse.",
|
|
15778
15485
|
' 4. Drill in: use `entries action=get entryId="..."` for full details \u2014 data, labels, relations, history.',
|
|
@@ -15784,7 +15491,7 @@ var INSTRUCTIONS = [
|
|
|
15784
15491
|
" 10. Close: call `session action=close` when done \u2014 records session activity. Auto-nudges if wrapup was skipped.",
|
|
15785
15492
|
"",
|
|
15786
15493
|
"Write tools (capture, update-entry, relations, commit-entry) require:",
|
|
15787
|
-
" - Call `
|
|
15494
|
+
" - Call `start_pb` to begin (starts session + loads context). Call `orient` mid-session for a refresh.",
|
|
15788
15495
|
" - A readwrite API key scope",
|
|
15789
15496
|
"",
|
|
15790
15497
|
"Commit-on-confirm: always capture as draft first and show the user what was captured.",
|
|
@@ -15833,7 +15540,9 @@ function createProductBrainServer() {
|
|
|
15833
15540
|
if (enabledModules.has("gitchain")) registerGitChainTools(server);
|
|
15834
15541
|
if (enabledModules.has("gitchain")) registerMapTools(server);
|
|
15835
15542
|
if (enabledModules.has("arch")) registerArchitectureTools(server);
|
|
15836
|
-
|
|
15543
|
+
registerSkillsTools(server);
|
|
15544
|
+
registerStartPbTools(server);
|
|
15545
|
+
registerRecordActivationTools(server);
|
|
15837
15546
|
registerUsageTools(server);
|
|
15838
15547
|
registerFacilitateTools(server);
|
|
15839
15548
|
registerAuditTools(server);
|
|
@@ -15870,6 +15579,11 @@ var WS = {
|
|
|
15870
15579
|
RANDY_PROD_PRIMARY: "mx784e9jjdqhsk2r0098bpvtes84e792",
|
|
15871
15580
|
RANDY_PROD_SECONDARY: "mx74q0mkwn4r920q2837qk7dsx84pq60"
|
|
15872
15581
|
};
|
|
15582
|
+
var USR = {
|
|
15583
|
+
RANDY: "user_3C31UK8CWugoDXUj2RGu1HDudas",
|
|
15584
|
+
/** Niels — full MVP unlock in any workspace (Clerk JWT `sub` / `FlagCtx.clerkUserId`). */
|
|
15585
|
+
NIELS: "user_3DOKRUaiY9KS3RKtC7shak3nB0N"
|
|
15586
|
+
};
|
|
15873
15587
|
var FLAGS = {
|
|
15874
15588
|
// WP-355 — MVP unlock: full Spine navigation (all modes + access patterns visible)
|
|
15875
15589
|
"mvp-unlock-spine": {
|
|
@@ -15879,7 +15593,8 @@ var FLAGS = {
|
|
|
15879
15593
|
WS.RANDY_DEV_SECONDARY,
|
|
15880
15594
|
WS.RANDY_PROD_PRIMARY,
|
|
15881
15595
|
WS.RANDY_PROD_SECONDARY
|
|
15882
|
-
]
|
|
15596
|
+
],
|
|
15597
|
+
userAllow: [USR.NIELS]
|
|
15883
15598
|
},
|
|
15884
15599
|
// WP-355 — MVP unlock: Bridge full view (beyond locked /bridge dashboard)
|
|
15885
15600
|
"mvp-unlock-bridge-full": {
|
|
@@ -15889,7 +15604,8 @@ var FLAGS = {
|
|
|
15889
15604
|
WS.RANDY_DEV_SECONDARY,
|
|
15890
15605
|
WS.RANDY_PROD_PRIMARY,
|
|
15891
15606
|
WS.RANDY_PROD_SECONDARY
|
|
15892
|
-
]
|
|
15607
|
+
],
|
|
15608
|
+
userAllow: [USR.NIELS]
|
|
15893
15609
|
},
|
|
15894
15610
|
// WP-355 — MVP unlock: Rail full context (beyond Rail context-only mode)
|
|
15895
15611
|
"mvp-unlock-rail-full": {
|
|
@@ -15899,7 +15615,8 @@ var FLAGS = {
|
|
|
15899
15615
|
WS.RANDY_DEV_SECONDARY,
|
|
15900
15616
|
WS.RANDY_PROD_PRIMARY,
|
|
15901
15617
|
WS.RANDY_PROD_SECONDARY
|
|
15902
|
-
]
|
|
15618
|
+
],
|
|
15619
|
+
userAllow: [USR.NIELS]
|
|
15903
15620
|
},
|
|
15904
15621
|
// WP-355 — MVP unlock: Import surface enabled
|
|
15905
15622
|
"mvp-unlock-import": {
|
|
@@ -15909,9 +15626,10 @@ var FLAGS = {
|
|
|
15909
15626
|
WS.RANDY_DEV_SECONDARY,
|
|
15910
15627
|
WS.RANDY_PROD_PRIMARY,
|
|
15911
15628
|
WS.RANDY_PROD_SECONDARY
|
|
15912
|
-
]
|
|
15629
|
+
],
|
|
15630
|
+
userAllow: [USR.NIELS]
|
|
15913
15631
|
},
|
|
15914
|
-
// WP-355 — Brain Chat:
|
|
15632
|
+
// WP-355 — Brain Chat: off by default; same workspace + user allowlists as other mvp-unlock flags.
|
|
15915
15633
|
"mvp-unlock-brain-chat": {
|
|
15916
15634
|
default: false,
|
|
15917
15635
|
workspaceAllow: [
|
|
@@ -15919,7 +15637,8 @@ var FLAGS = {
|
|
|
15919
15637
|
WS.RANDY_DEV_SECONDARY,
|
|
15920
15638
|
WS.RANDY_PROD_PRIMARY,
|
|
15921
15639
|
WS.RANDY_PROD_SECONDARY
|
|
15922
|
-
]
|
|
15640
|
+
],
|
|
15641
|
+
userAllow: [USR.NIELS]
|
|
15923
15642
|
},
|
|
15924
15643
|
// WP-373 E3 — Routing Resolver SSOT killswitch.
|
|
15925
15644
|
// When true, the new pure resolver short-circuits and mount sites execute the
|
|
@@ -15955,4 +15674,4 @@ export {
|
|
|
15955
15674
|
createProductBrainServer,
|
|
15956
15675
|
initFeatureFlags
|
|
15957
15676
|
};
|
|
15958
|
-
//# sourceMappingURL=chunk-
|
|
15677
|
+
//# sourceMappingURL=chunk-DNZQANRI.js.map
|