snipara-companion 3.2.26 → 3.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +24 -4
- package/dist/index.js +68 -12
- package/docs/FULL_REFERENCE.md +19 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Release notes for `snipara-companion`, newest first.
|
|
4
4
|
|
|
5
|
+
## New In 3.2.28
|
|
6
|
+
|
|
7
|
+
- Adds bounded Outcome Loop retrieval correlation to generated Codex, Claude,
|
|
8
|
+
Cursor, VS Code/Continue, and generic HTTP MCP references through
|
|
9
|
+
`SNIPARA_SESSION_ID` / `X-Snipara-Session-Id`.
|
|
10
|
+
- Prints the active correlation session during `init` and documents the
|
|
11
|
+
explicit `correlation_context.session_id` fallback for MCP clients that
|
|
12
|
+
cannot inject environment-backed headers.
|
|
13
|
+
- Keeps the correlation identifier telemetry-only and project-scoped; it does
|
|
14
|
+
not alter authentication or authorization.
|
|
15
|
+
|
|
16
|
+
## New In 3.2.27
|
|
17
|
+
|
|
18
|
+
- Scopes `context-control drift` dirty Git detection to the ProjectContext
|
|
19
|
+
manifest, declared manifest sources, local Decision Requests, and
|
|
20
|
+
`.snipara/context-control/` artifacts. Dirty files outside that scope remain
|
|
21
|
+
visible but no longer force permanent `DRIFT_DETECTED` in shared checkouts.
|
|
22
|
+
- Clarifies the Context as Code V0 boundary: manifests are local declarative
|
|
23
|
+
metadata and do not refresh hosted context, reconcile manifest-vs-hosted
|
|
24
|
+
state, or mutate hosted memory. Hosted refresh/apply remains V1 work.
|
|
25
|
+
- Adds executable documentation smoke tests that run the README and full
|
|
26
|
+
reference `context-control` examples against the built local CLI, closing the
|
|
27
|
+
loop that previously allowed narrative examples to drift from command reality.
|
|
28
|
+
|
|
5
29
|
## New In 3.2.26
|
|
6
30
|
|
|
7
31
|
- Fixes the published `context-control plan` examples so they use the real CLI
|
package/README.md
CHANGED
|
@@ -49,6 +49,18 @@ Use this after `create-snipara` activation. `create-snipara` remains the
|
|
|
49
49
|
canonical engine for first workspace setup; Companion owns the repeatable local
|
|
50
50
|
continuity loop after that.
|
|
51
51
|
|
|
52
|
+
## Retrieval And Outcome Correlation
|
|
53
|
+
|
|
54
|
+
`snipara-companion init` creates and prints a bounded workspace session ID.
|
|
55
|
+
Export it as `SNIPARA_SESSION_ID` before starting Codex, Claude, Cursor, VS Code,
|
|
56
|
+
Continue, or another HTTP MCP client. Generated configs forward it as
|
|
57
|
+
`X-Snipara-Session-Id`, while canonical execution events use the same Companion
|
|
58
|
+
session automatically. Clients without environment-backed headers can pass the
|
|
59
|
+
same value as `correlation_context.session_id` on retrieval tools.
|
|
60
|
+
|
|
61
|
+
The identifier is opaque, project-scoped telemetry. It does not grant access or
|
|
62
|
+
change authorization, and explicit per-call correlation remains authoritative.
|
|
63
|
+
|
|
52
64
|
Example output excerpt:
|
|
53
65
|
|
|
54
66
|
```text
|
|
@@ -99,7 +111,8 @@ These commands are useful without hosted Snipara:
|
|
|
99
111
|
`context-control` is the local trust layer for Project Intelligence state. It
|
|
100
112
|
borrows Terraform's useful product grammar without copying Terraform: preview a
|
|
101
113
|
bounded context mutation, inspect drift, then apply only the exact reviewed
|
|
102
|
-
plan.
|
|
114
|
+
plan. V0 is intentionally local-only: it creates trust artifacts for review, not
|
|
115
|
+
hosted context mutations.
|
|
103
116
|
|
|
104
117
|
```bash
|
|
105
118
|
npx -y snipara-companion context-control plan \
|
|
@@ -123,13 +136,15 @@ locally:
|
|
|
123
136
|
"path": "docs/architecture.md",
|
|
124
137
|
"authority": "canonical",
|
|
125
138
|
"tier": "HOT",
|
|
126
|
-
"
|
|
139
|
+
"required": true,
|
|
140
|
+
"description": "Architecture context that agents should treat as canonical."
|
|
127
141
|
}
|
|
128
142
|
],
|
|
129
143
|
"policies": [
|
|
130
144
|
{
|
|
131
145
|
"id": "review-context-changes",
|
|
132
|
-
"
|
|
146
|
+
"scope": "memory.canonical",
|
|
147
|
+
"requirement": "Human review required before changing canonical context.",
|
|
133
148
|
"reviewRequired": true
|
|
134
149
|
}
|
|
135
150
|
]
|
|
@@ -142,7 +157,12 @@ npx -y snipara-companion context-control plan --manifest snipara.project-context
|
|
|
142
157
|
```
|
|
143
158
|
|
|
144
159
|
The manifest is declarative metadata only. Validation and local reconciliation
|
|
145
|
-
do not upload documents, approve memory,
|
|
160
|
+
do not upload documents, approve memory, refresh hosted context, or mutate
|
|
161
|
+
hosted Snipara state. `context-control drift` scopes dirty Git signals to the
|
|
162
|
+
manifest, manifest sources, local Decision Requests, and `.snipara/context-control/`
|
|
163
|
+
artifacts so unrelated checkout noise does not become permanent drift. A future
|
|
164
|
+
V1 hosted refresh/apply surface should compare manifest state against hosted
|
|
165
|
+
context before allowing real hosted mutations.
|
|
146
166
|
|
|
147
167
|
### Local Worker Registry
|
|
148
168
|
|
package/dist/index.js
CHANGED
|
@@ -5641,6 +5641,7 @@ Claude Code should apply this workflow automatically for project-specific work;
|
|
|
5641
5641
|
|
|
5642
5642
|
- Bound Snipara project: \`${projectSlug}\`
|
|
5643
5643
|
- Hosted MCP endpoint: \`${buildHostedMcpEndpoint(projectSlug)}\`
|
|
5644
|
+
- Keep \`SNIPARA_SESSION_ID\` equal to the active Companion session. Generated MCP configs send it as \`X-Snipara-Session-Id\`; when a client cannot send that header, pass the same value as \`correlation_context.session_id\` on retrieval tools.
|
|
5644
5645
|
- At the start of substantial work, validate the hosted MCP surface with a tool-oriented call, then use \`snipara_recall\` and a targeted \`snipara_context_query\` before falling back to local search.
|
|
5645
5646
|
- Do not treat empty MCP resources/templates as an outage. If the tool surface looks incomplete, call \`snipara_help(list_all=true)\` and compare exact tool names.
|
|
5646
5647
|
- Use \`snipara_context_query\` for docs, business context, architecture notes, runbooks, and source truth. Use \`snipara_get_chunk\` for exact cited sections when references are returned.
|
|
@@ -5660,6 +5661,7 @@ function buildSharedWorkflowInstructionBlock(projectSlug, client) {
|
|
|
5660
5661
|
This workspace is bound to Snipara project \`${projectSlug}\` for ${formatClientName(client)}. Agents should use Snipara automatically for project-specific context, decisions, and workflow state.
|
|
5661
5662
|
|
|
5662
5663
|
- Hosted MCP endpoint: \`${buildHostedMcpEndpoint(projectSlug)}\`
|
|
5664
|
+
- Keep \`SNIPARA_SESSION_ID\` equal to the active Companion session. Generated MCP configs send it as \`X-Snipara-Session-Id\`; when a client cannot send that header, pass the same value as \`correlation_context.session_id\` on retrieval tools.
|
|
5663
5665
|
- At the start of substantial work, validate the hosted MCP surface with a tool-oriented call, then use \`snipara_recall\` and a targeted \`snipara_context_query\` before falling back to local search.
|
|
5664
5666
|
- Do not treat empty MCP resources/templates as an outage. If the tool surface looks incomplete, call \`snipara_help(list_all=true)\` and compare exact tool names.
|
|
5665
5667
|
- Use \`snipara_context_query\` for docs, business context, architecture notes, runbooks, and source truth. Use \`snipara_get_chunk\` for exact cited sections when references are returned.
|
|
@@ -5911,6 +5913,7 @@ function generateCodexConfigString(projectSlug) {
|
|
|
5911
5913
|
type = "streamable_http"
|
|
5912
5914
|
url = "https://api.snipara.com/mcp/${projectSlug}"
|
|
5913
5915
|
bearer_token_env_var = "SNIPARA_API_KEY"
|
|
5916
|
+
env_http_headers = { "X-Snipara-Session-Id" = "SNIPARA_SESSION_ID" }
|
|
5914
5917
|
`;
|
|
5915
5918
|
}
|
|
5916
5919
|
function generateGenericMcpReferenceString(client, projectSlug) {
|
|
@@ -5920,7 +5923,8 @@ function generateGenericMcpReferenceString(client, projectSlug) {
|
|
|
5920
5923
|
name: "snipara",
|
|
5921
5924
|
url: `https://api.snipara.com/mcp/${projectSlug}`,
|
|
5922
5925
|
headers: {
|
|
5923
|
-
"X-API-Key": "${SNIPARA_API_KEY}"
|
|
5926
|
+
"X-API-Key": "${SNIPARA_API_KEY}",
|
|
5927
|
+
"X-Snipara-Session-Id": "${SNIPARA_SESSION_ID}"
|
|
5924
5928
|
}
|
|
5925
5929
|
},
|
|
5926
5930
|
null,
|
|
@@ -5935,7 +5939,8 @@ function generateGenericMcpReferenceString(client, projectSlug) {
|
|
|
5935
5939
|
type: "http",
|
|
5936
5940
|
url: `https://api.snipara.com/mcp/${projectSlug}`,
|
|
5937
5941
|
headers: {
|
|
5938
|
-
"X-API-Key": "${SNIPARA_API_KEY}"
|
|
5942
|
+
"X-API-Key": "${SNIPARA_API_KEY}",
|
|
5943
|
+
"X-Snipara-Session-Id": "${SNIPARA_SESSION_ID}"
|
|
5939
5944
|
}
|
|
5940
5945
|
}
|
|
5941
5946
|
}
|
|
@@ -5995,6 +6000,7 @@ async function callSniparaTool(name: string, args: Record<string, unknown>): Pro
|
|
|
5995
6000
|
headers: {
|
|
5996
6001
|
"Content-Type": "application/json",
|
|
5997
6002
|
"X-API-Key": SNIPARA_API_KEY,
|
|
6003
|
+
"X-Snipara-Session-Id": process.env.SNIPARA_SESSION_ID || "",
|
|
5998
6004
|
},
|
|
5999
6005
|
body: JSON.stringify({
|
|
6000
6006
|
jsonrpc: "2.0",
|
|
@@ -6561,12 +6567,13 @@ async function initCommand(options) {
|
|
|
6561
6567
|
existingProjectId: existingConfig.projectId,
|
|
6562
6568
|
selectedProject
|
|
6563
6569
|
});
|
|
6570
|
+
const sessionId = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
6564
6571
|
saveConfig(
|
|
6565
6572
|
{
|
|
6566
6573
|
apiKey,
|
|
6567
6574
|
apiUrl: existingConfig.apiUrl,
|
|
6568
6575
|
projectId: projectIdentifier,
|
|
6569
|
-
sessionId
|
|
6576
|
+
sessionId,
|
|
6570
6577
|
client: selectedClient
|
|
6571
6578
|
},
|
|
6572
6579
|
{ cwd: projectDir, scope: "workspace" }
|
|
@@ -6688,6 +6695,10 @@ async function initCommand(options) {
|
|
|
6688
6695
|
console.log(`Companion config: ${configPaths.companion}`);
|
|
6689
6696
|
console.log(`Selected project: ${formatProjectChoice(selectedProject)}`);
|
|
6690
6697
|
console.log(`Selected client: ${formatClientName(selectedClient)}`);
|
|
6698
|
+
console.log(`Correlation session: ${sessionId}`);
|
|
6699
|
+
console.log(
|
|
6700
|
+
"Export SNIPARA_SESSION_ID with this value before starting the MCP client so served context and execution outcomes share one bounded session."
|
|
6701
|
+
);
|
|
6691
6702
|
if (options.withHooks) {
|
|
6692
6703
|
console.log("\nNext steps:");
|
|
6693
6704
|
if (hookClient === "cursor") {
|
|
@@ -33089,6 +33100,35 @@ function parseDirtyFile2(line) {
|
|
|
33089
33100
|
const renameParts = withoutStatus.split(" -> ");
|
|
33090
33101
|
return renameParts[renameParts.length - 1]?.replace(/^"|"$/g, "");
|
|
33091
33102
|
}
|
|
33103
|
+
function resolveGitDriftScopePaths(cwd) {
|
|
33104
|
+
const scopePaths = [
|
|
33105
|
+
PROJECT_CONTEXT_MANIFEST_DEFAULT_PATH,
|
|
33106
|
+
CONTEXT_CONTROL_RELATIVE_DIR,
|
|
33107
|
+
path30.join(".snipara", "decisions")
|
|
33108
|
+
];
|
|
33109
|
+
const manifestPath = resolveManifestPath(cwd);
|
|
33110
|
+
if (!fs30.existsSync(manifestPath)) {
|
|
33111
|
+
return normalizeScopePaths(scopePaths, cwd);
|
|
33112
|
+
}
|
|
33113
|
+
const manifestRead = readJsonFileSafe(manifestPath);
|
|
33114
|
+
if (!manifestRead.value) {
|
|
33115
|
+
return normalizeScopePaths(scopePaths, cwd);
|
|
33116
|
+
}
|
|
33117
|
+
const validation = validateProjectContextManifest({ manifest: manifestRead.value });
|
|
33118
|
+
const manifestSourcePaths = validation.manifest?.sources.map((source2) => source2.path) ?? [];
|
|
33119
|
+
return normalizeScopePaths([...scopePaths, ...manifestSourcePaths], cwd);
|
|
33120
|
+
}
|
|
33121
|
+
function normalizeScopePaths(scopePaths, cwd) {
|
|
33122
|
+
return uniqueStrings20(
|
|
33123
|
+
scopePaths.map((scopePath) => normalizeProjectRelativePath(scopePath, cwd).replace(/\/+$/g, ""))
|
|
33124
|
+
);
|
|
33125
|
+
}
|
|
33126
|
+
function isPathInsideScope(filePath, scopePaths) {
|
|
33127
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/\/+$/g, "");
|
|
33128
|
+
return scopePaths.some(
|
|
33129
|
+
(scopePath) => normalized === scopePath || normalized.startsWith(`${scopePath}/`)
|
|
33130
|
+
);
|
|
33131
|
+
}
|
|
33092
33132
|
function slugify2(value) {
|
|
33093
33133
|
const slug2 = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
33094
33134
|
return slug2 || "context-control-state";
|
|
@@ -33380,25 +33420,39 @@ function readJsonFileSafe(filePath) {
|
|
|
33380
33420
|
}
|
|
33381
33421
|
function collectGitDriftSignals(cwd) {
|
|
33382
33422
|
const revision = resolveGitBaseRevision(cwd);
|
|
33423
|
+
const scopePaths = resolveGitDriftScopePaths(cwd);
|
|
33424
|
+
const relevantDirtyFiles = revision.dirtyFiles.filter(
|
|
33425
|
+
(file) => isPathInsideScope(file, scopePaths)
|
|
33426
|
+
);
|
|
33383
33427
|
const upstream = runGit3(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd);
|
|
33384
33428
|
const upstreamCounts = upstream ? runGit3(["rev-list", "--left-right", "--count", `${upstream}...HEAD`], cwd) : void 0;
|
|
33385
33429
|
const [behind = 0, ahead = 0] = upstreamCounts ? upstreamCounts.split(/\s+/g).map((part) => Number.parseInt(part, 10) || 0) : [];
|
|
33386
|
-
const dirtySignal = revision.dirty ? {
|
|
33387
|
-
id: "git-working-tree-dirty",
|
|
33430
|
+
const dirtySignal = revision.dirty ? relevantDirtyFiles.length > 0 ? {
|
|
33431
|
+
id: "git-working-tree-dirty-relevant",
|
|
33388
33432
|
surface: "git",
|
|
33389
33433
|
state: "DRIFT_DETECTED",
|
|
33390
|
-
summary: "Working tree has
|
|
33391
|
-
expected: "
|
|
33392
|
-
observed:
|
|
33393
|
-
refs:
|
|
33434
|
+
summary: "Working tree has uncommitted changes in context-control or ProjectContext scope.",
|
|
33435
|
+
expected: "Manifest, manifest sources, Decision Requests, and context-control artifacts are clean.",
|
|
33436
|
+
observed: relevantDirtyFiles.join(", "),
|
|
33437
|
+
refs: relevantDirtyFiles,
|
|
33394
33438
|
severity: "watch",
|
|
33395
|
-
reasonCodes: ["
|
|
33439
|
+
reasonCodes: ["git_working_tree_dirty_relevant"]
|
|
33440
|
+
} : {
|
|
33441
|
+
id: "git-working-tree-dirty-out-of-scope",
|
|
33442
|
+
surface: "git",
|
|
33443
|
+
state: "IN_SYNC",
|
|
33444
|
+
summary: "Working tree has uncommitted changes, but none are in context-control or ProjectContext scope.",
|
|
33445
|
+
expected: `Only scoped dirty paths affect context-control drift: ${scopePaths.join(", ")}.`,
|
|
33446
|
+
observed: `${revision.dirtyFiles.length} out-of-scope dirty file(s)`,
|
|
33447
|
+
refs: revision.dirtyFiles,
|
|
33448
|
+
severity: "info",
|
|
33449
|
+
reasonCodes: ["git_working_tree_dirty_out_of_scope"]
|
|
33396
33450
|
} : {
|
|
33397
33451
|
id: "git-working-tree-clean",
|
|
33398
33452
|
surface: "git",
|
|
33399
33453
|
state: "IN_SYNC",
|
|
33400
|
-
summary: "Working tree
|
|
33401
|
-
refs:
|
|
33454
|
+
summary: "Working tree has no uncommitted changes.",
|
|
33455
|
+
refs: scopePaths,
|
|
33402
33456
|
severity: "info",
|
|
33403
33457
|
reasonCodes: ["git_working_tree_clean"]
|
|
33404
33458
|
};
|
|
@@ -33645,6 +33699,8 @@ function buildLocalProjectDriftReport(options = {}) {
|
|
|
33645
33699
|
],
|
|
33646
33700
|
caveats: [
|
|
33647
33701
|
"Project Drift V0 is a read-only local report.",
|
|
33702
|
+
"Git dirty-file drift is scoped to the ProjectContext manifest, manifest sources, Decision Requests, and .snipara/context-control artifacts; use git status for full working-tree release readiness.",
|
|
33703
|
+
"Project Drift V0 validates manifest health locally but does not refresh hosted context state; hosted manifest-vs-context reconciliation is V1 work.",
|
|
33648
33704
|
"UNKNOWN is never treated as IN_SYNC; verify missing or unreadable evidence before applying changes."
|
|
33649
33705
|
]
|
|
33650
33706
|
});
|
package/docs/FULL_REFERENCE.md
CHANGED
|
@@ -339,13 +339,18 @@ snipara-companion workflow resume --include-session-context
|
|
|
339
339
|
Git bases by default, writes only under `.snipara/context-control/`, and emits
|
|
340
340
|
an apply receipt linked to the plan hash.
|
|
341
341
|
- `context-control drift` is a read-only Project Drift V0 report. It checks Git
|
|
342
|
-
|
|
343
|
-
Requests, saved plans and receipts, and ProjectContext manifest
|
|
344
|
-
|
|
342
|
+
upstream state, scoped Git dirty files, managed workflow state, pending
|
|
343
|
+
Decision Requests, saved plans and receipts, and ProjectContext manifest
|
|
344
|
+
health. Dirty Git files only become drift when they touch the ProjectContext
|
|
345
|
+
manifest, manifest sources, local Decision Requests, or
|
|
346
|
+
`.snipara/context-control/`; unrelated checkout noise remains visible without
|
|
347
|
+
classifying the project as `DRIFT_DETECTED`. `UNKNOWN` is never treated as
|
|
348
|
+
`IN_SYNC`.
|
|
345
349
|
- `context-control validate --manifest snipara.project-context.json` validates
|
|
346
350
|
Context as Code V0. The manifest is JSON metadata declaring context sources,
|
|
347
|
-
tiers, authority,
|
|
348
|
-
|
|
351
|
+
tiers, authority, freshness, and review policies. It does not upload content,
|
|
352
|
+
refresh hosted context, reconcile manifest-vs-hosted state, or mutate hosted
|
|
353
|
+
Snipara state; hosted refresh/apply belongs to the future V1 surface.
|
|
349
354
|
- `lead-plan` turns local workflow state, Team Sync, file scope, context refs,
|
|
350
355
|
proof gates, and acceptance criteria into an advisory Engineering Lead Plan.
|
|
351
356
|
It emits worker recommendations and handoff contracts, keeps
|
|
@@ -816,6 +821,15 @@ Mistral generates MCP-first files (`MISTRAL.md`, Vibe config, Le Chat connector
|
|
|
816
821
|
reference, and LangChain `ChatMistralAI.bindTools` snippets); Mistral request
|
|
817
822
|
hooks are model request hooks, not local agent lifecycle hooks.
|
|
818
823
|
|
|
824
|
+
All generated HTTP/SSE references include a bounded correlation header sourced
|
|
825
|
+
from `SNIPARA_SESSION_ID`. Set it to the session printed by `init` before
|
|
826
|
+
starting Codex, Claude, Cursor, VS Code/Continue, GLM, or another generic MCP
|
|
827
|
+
host. The hosted server uses the header only for project-scoped retrieval
|
|
828
|
+
telemetry and ignores invalid values. If the host cannot inject environment
|
|
829
|
+
headers, pass the same value as `correlation_context.session_id` on
|
|
830
|
+
`snipara_context_query`, `snipara_recall`, `snipara_search`, `snipara_ask`, and
|
|
831
|
+
`snipara_get_chunk`.
|
|
832
|
+
|
|
819
833
|
OpenClaw hooks remain separate:
|
|
820
834
|
|
|
821
835
|
```bash
|