snipara-companion 3.2.13 → 3.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,27 @@ npx -y snipara-companion impact src/auth/session.ts --source local
22
22
  npx -y snipara-companion source init .
23
23
  ```
24
24
 
25
+ ## Companion Continuity Contract V1
26
+
27
+ Editor integrations and post-activation workflows can ask Companion for one
28
+ machine-readable "continue this workspace" payload:
29
+
30
+ ```bash
31
+ npx -y snipara-companion@latest continue-workspace --include-session-context --json
32
+ ```
33
+
34
+ The payload version is `snipara.companion.continuity.v1`. It is designed for
35
+ native editor commands, status bars, panels, and agent handoffs that need to
36
+ resume real work without rescanning or reimplementing Snipara semantics. It
37
+ includes project binding, session bootstrap entries and quality warnings,
38
+ workflow phase state, Team Sync handoff summary, passive source snapshot status,
39
+ session snapshot summary, stable local artifact paths, and recommended next
40
+ actions.
41
+
42
+ Use this after `create-snipara` activation. `create-snipara` remains the
43
+ canonical engine for first workspace setup; Companion owns the repeatable local
44
+ continuity loop after that.
45
+
25
46
  Example output excerpt:
26
47
 
27
48
  ```text
@@ -56,6 +77,7 @@ These commands are useful without hosted Snipara:
56
77
  | `workflow start` / `phase-start` / `phase-commit` / `resume` | Agent continuity that survives compaction |
57
78
  | `workflow timeline` / `workflow session` | Append-only local activity log and Session Snapshot V0 |
58
79
  | `workflow decisions` / `workflow decide` | Local human decision requests and response receipts |
80
+ | `run --emit-policy-decisions` | Project Policy review requests in the agent workflow |
59
81
  | `workflow producer-triage` | Ask for human review of unreviewed Producer Loop samples |
60
82
  | `workflow producer-report` | Local Producer Loop adoption and calibration report |
61
83
  | `workflow producer-review` | Mark local Producer Loop samples reviewed or rejected |
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ProjectIntentDetectionResult, ProjectPolicyDecision, ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary, ProjectPolicyRule, OutcomeIntelligenceCalibration, ControlledWorkerExecutionMode, ControlledWorkerExecutionReceipt } from '@snipara/project-intelligence-contracts';
2
+ import { ProjectIntentDetectionResult, ProjectPolicyDecision, ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary, ProjectPolicyRule, DecisionRequest, OutcomeIntelligenceCalibration, ControlledWorkerExecutionMode, ControlledWorkerExecutionReceipt } from '@snipara/project-intelligence-contracts';
3
3
 
4
4
  declare function resolveQueryFromToolInput(toolInput?: string, tool?: string): string | null;
5
5
 
@@ -2073,6 +2073,14 @@ interface RealityCheckCommandOptions {
2073
2073
  declare function buildLocalProjectRealityCheck(options: RealityCheckCommandOptions): ProjectRealityCheckResult;
2074
2074
  declare function realityCheckCommand(options: RealityCheckCommandOptions): Promise<void>;
2075
2075
 
2076
+ interface DecisionRequestWriteResult {
2077
+ status: "written" | "duplicate_pending" | "duplicate_resolved";
2078
+ requestId: string;
2079
+ fingerprint: string;
2080
+ path?: string;
2081
+ relativePath?: string;
2082
+ }
2083
+
2076
2084
  interface MemoryHealthCommandOptions {
2077
2085
  scope?: MemoryScope;
2078
2086
  includeInactive?: boolean;
@@ -2463,6 +2471,7 @@ interface ProjectRunCommandOptions {
2463
2471
  servedJudgmentId?: string;
2464
2472
  skipAdvisorReceipts?: boolean;
2465
2473
  outcomeReceiptFiles?: string[];
2474
+ emitPolicyDecisions?: boolean;
2466
2475
  json?: boolean;
2467
2476
  }
2468
2477
  interface ProjectRunGuardResult {
@@ -2509,11 +2518,20 @@ interface ProjectIntelligenceRunResult {
2509
2518
  guard?: ProjectRunGuardResult;
2510
2519
  packageReview?: ProjectRunPackageReview;
2511
2520
  policyGates: ProjectPolicyGatesResult;
2521
+ policyDecisionRequests?: ProjectRunPolicyDecisionRequests;
2512
2522
  advisorReceiptCapture?: ProjectRunAdvisorReceiptCapture;
2513
2523
  outcomeCalibration?: OutcomeIntelligenceCalibration;
2514
2524
  judgmentCard: ProjectIntelligenceJudgmentCard;
2515
2525
  suggestedCommands: string[];
2516
2526
  }
2527
+ interface ProjectRunPolicyDecisionRequests {
2528
+ version: "project-intelligence.policy-decision-requests.v1";
2529
+ emitted: boolean;
2530
+ requestCount: number;
2531
+ requests: DecisionRequest[];
2532
+ writes: DecisionRequestWriteResult[];
2533
+ caveats: string[];
2534
+ }
2517
2535
  declare function buildProjectIntelligenceRun(options: ProjectRunCommandOptions): Promise<ProjectIntelligenceRunResult>;
2518
2536
  declare function projectIntelligenceRunCommand(options: ProjectRunCommandOptions): Promise<void>;
2519
2537
 
package/dist/index.js CHANGED
@@ -16928,6 +16928,7 @@ var MIN_WORKFLOW_SURFACE_TOKENS = 200;
16928
16928
  var STALE_BOOTSTRAP_MEMORY_DAYS = 90;
16929
16929
  var TASK_COMMIT_TIMEOUT_MS = 3e4;
16930
16930
  var FINAL_COMMIT_TIMEOUT_MS = 9e4;
16931
+ var COMPANION_CONTINUITY_CONTRACT_VERSION = "snipara.companion.continuity.v1";
16931
16932
  var FINAL_COMMIT_RETRY_TIMEOUT_MS = 45e3;
16932
16933
  var FINAL_COMMIT_SUMMARY_MAX_CHARS = 1200;
16933
16934
  var FINAL_COMMIT_RETRY_SUMMARY_MAX_CHARS = 600;
@@ -24482,6 +24483,191 @@ async function sessionBootstrapCommand(options) {
24482
24483
  printSessionBootstrapQuality(buildPrintedBootstrapQuality(printedBootstrap));
24483
24484
  }
24484
24485
  }
24486
+ function emptySessionMemoriesResult() {
24487
+ return {
24488
+ critical: { memories: [], count: 0, tokens: 0 },
24489
+ daily: { memories: [], count: 0, tokens: 0 },
24490
+ total_tokens: 0
24491
+ };
24492
+ }
24493
+ function readLocalJsonFile(relativePath2) {
24494
+ const absolutePath = path20.join(process.cwd(), relativePath2);
24495
+ if (!fs21.existsSync(absolutePath)) {
24496
+ return null;
24497
+ }
24498
+ try {
24499
+ const parsed = JSON.parse(fs21.readFileSync(absolutePath, "utf-8"));
24500
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
24501
+ } catch {
24502
+ return null;
24503
+ }
24504
+ }
24505
+ function summarizeSourceSnapshot(snapshot) {
24506
+ if (!snapshot) {
24507
+ return {
24508
+ status: "missing",
24509
+ snapshotPath: path20.join(".snipara", "source", "latest.json"),
24510
+ nextAction: "Run snipara-companion source sync --json after meaningful local code or docs movement."
24511
+ };
24512
+ }
24513
+ const summary = snapshot.summary && typeof snapshot.summary === "object" && !Array.isArray(snapshot.summary) ? snapshot.summary : {};
24514
+ return {
24515
+ status: "present",
24516
+ snapshotPath: path20.join(".snipara", "source", "latest.json"),
24517
+ generatedAt: snapshot.generatedAt,
24518
+ revision: snapshot.revision,
24519
+ totalFiles: summary.totalFiles,
24520
+ totalBytes: summary.totalBytes,
24521
+ skipped: summary.skipped
24522
+ };
24523
+ }
24524
+ function summarizeWorkflowStateForContinuity(workflow2) {
24525
+ if (!workflow2) {
24526
+ return {
24527
+ status: "missing",
24528
+ statePath: WORKFLOW_STATE_RELATIVE_PATH,
24529
+ nextAction: "Run snipara-companion workflow start for multi-phase work."
24530
+ };
24531
+ }
24532
+ return {
24533
+ status: workflow2.status,
24534
+ workflowId: workflow2.workflowId,
24535
+ goal: workflow2.goal,
24536
+ currentPhaseId: workflow2.currentPhaseId,
24537
+ statePath: WORKFLOW_STATE_RELATIVE_PATH,
24538
+ phases: workflow2.phases.map((phase) => ({
24539
+ id: phase.id,
24540
+ title: phase.title,
24541
+ status: phase.status
24542
+ }))
24543
+ };
24544
+ }
24545
+ function summarizeTeamSyncForContinuity() {
24546
+ const state = loadTeamSyncState();
24547
+ const summary = buildTeamSyncSummary(state);
24548
+ const latestHandoff = state.handoffs.at(-1);
24549
+ return {
24550
+ statePath: path20.relative(process.cwd(), getTeamSyncStatePath()),
24551
+ activeWorkCount: summary.activeWorkCount,
24552
+ staleWorkCount: summary.staleWorkCount,
24553
+ completedWorkCount: summary.completedWorkCount,
24554
+ archivedWorkCount: summary.archivedWorkCount,
24555
+ handoffCount: summary.handoffCount,
24556
+ latestHandoff: latestHandoff ? {
24557
+ summary: latestHandoff.summary,
24558
+ next: latestHandoff.next,
24559
+ attention: latestHandoff.attention,
24560
+ createdAt: latestHandoff.createdAt
24561
+ } : null
24562
+ };
24563
+ }
24564
+ function buildCompanionContinuityPayload(args) {
24565
+ const config = args.configured ? loadConfig() : null;
24566
+ const workflow2 = readWorkflowState2();
24567
+ const sessionSnapshot = readSessionSnapshot();
24568
+ const sourceSnapshot = readLocalJsonFile(path20.join(".snipara", "source", "latest.json"));
24569
+ return {
24570
+ version: COMPANION_CONTINUITY_CONTRACT_VERSION,
24571
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
24572
+ configured: args.configured,
24573
+ project: config ? {
24574
+ projectId: config.projectId,
24575
+ apiUrl: config.apiUrl,
24576
+ sessionId: config.sessionId
24577
+ } : null,
24578
+ artifacts: {
24579
+ workflowStatePath: WORKFLOW_STATE_RELATIVE_PATH,
24580
+ teamSyncStatePath: path20.relative(process.cwd(), getTeamSyncStatePath()),
24581
+ sessionSnapshotPath: path20.join(".snipara", "activity", "session.json"),
24582
+ sourceSnapshotPath: path20.join(".snipara", "source", "latest.json")
24583
+ },
24584
+ bootstrap: args.bootstrap,
24585
+ bootstrapQuality: args.bootstrapQuality,
24586
+ localWarmSnapshot: {
24587
+ storedEntries: args.warmSnapshotStoredEntries
24588
+ },
24589
+ sessionContext: {
24590
+ included: args.includeSessionContext,
24591
+ maxTokens: args.maxContextTokens
24592
+ },
24593
+ workflow: summarizeWorkflowStateForContinuity(workflow2),
24594
+ teamSync: summarizeTeamSyncForContinuity(),
24595
+ source: summarizeSourceSnapshot(sourceSnapshot),
24596
+ sessionSnapshot: sessionSnapshot ? {
24597
+ latestActivityAt: sessionSnapshot.summary.latestActivityAt,
24598
+ latestTitle: sessionSnapshot.summary.latestActivityTitle,
24599
+ latestKind: sessionSnapshot.summary.latestActivityKind,
24600
+ risk: sessionSnapshot.summary.risk,
24601
+ riskReasons: sessionSnapshot.summary.riskReasons,
24602
+ touchedFiles: sessionSnapshot.summary.touchedFiles,
24603
+ nextAction: sessionSnapshot.summary.recommendedNextAction
24604
+ } : null,
24605
+ nextActions: [
24606
+ {
24607
+ id: "open_bootstrap_brief",
24608
+ label: "Open bootstrap brief",
24609
+ command: "snipara-companion session-bootstrap --include-session-context --max-context-tokens 1000",
24610
+ when: "bootstrap has entries or session context is included"
24611
+ },
24612
+ {
24613
+ id: "refresh_source_snapshot",
24614
+ label: "Refresh source snapshot",
24615
+ command: "snipara-companion source sync --json",
24616
+ when: "source.status is missing or local code/docs changed"
24617
+ },
24618
+ {
24619
+ id: "run_impact_gate",
24620
+ label: "Run code impact before risky edits",
24621
+ command: 'snipara-companion code impact --changed-files <files> --diff-summary "next edit"',
24622
+ when: "the next edit is multi-file, risky, or user-visible"
24623
+ },
24624
+ {
24625
+ id: "commit_task_context",
24626
+ label: "Commit durable task context",
24627
+ command: 'snipara-companion task-commit --summary "<done>" --files <files>',
24628
+ when: "a durable phase or task is complete"
24629
+ }
24630
+ ]
24631
+ };
24632
+ }
24633
+ async function continueWorkspaceCommand(options) {
24634
+ const configured = isConfigured();
24635
+ const resolvedContextTokens = options.maxContextTokens !== void 0 ? options.maxContextTokens : options.includeSessionContext ? DEFAULT_SESSION_CONTEXT_TOKENS : 0;
24636
+ let bootstrap = emptySessionMemoriesResult();
24637
+ let warmSnapshotStoredEntries = 0;
24638
+ if (configured) {
24639
+ const client = createClient(15e3);
24640
+ bootstrap = await client.getSessionMemories(options.maxCriticalTokens, resolvedContextTokens);
24641
+ const config = loadConfig();
24642
+ const warmSnapshot = createLocalQueryCache({
24643
+ cwd: process.cwd(),
24644
+ projectId: config.projectId,
24645
+ sessionId: config.sessionId
24646
+ }).storeWarmSnapshot(bootstrap);
24647
+ warmSnapshotStoredEntries = warmSnapshot.storedEntries;
24648
+ }
24649
+ const bootstrapQuality = buildSessionBootstrapQuality(bootstrap, {
24650
+ expectedMaxTokens: (options.maxCriticalTokens ?? DEFAULT_FULL_WORKFLOW_CRITICAL_TOKENS) + resolvedContextTokens
24651
+ });
24652
+ const payload = buildCompanionContinuityPayload({
24653
+ configured,
24654
+ bootstrap,
24655
+ bootstrapQuality,
24656
+ includeSessionContext: resolvedContextTokens > 0,
24657
+ maxContextTokens: resolvedContextTokens,
24658
+ warmSnapshotStoredEntries
24659
+ });
24660
+ if (options.json) {
24661
+ printJson2(payload);
24662
+ return;
24663
+ }
24664
+ console.log(import_chalk6.default.bold("Snipara Companion Continuity"));
24665
+ console.log(`Version: ${COMPANION_CONTINUITY_CONTRACT_VERSION}`);
24666
+ console.log(`Configured: ${configured ? "yes" : "no"}`);
24667
+ console.log(`Workflow: ${payload.workflow.status ?? "unknown"}`);
24668
+ console.log(`Source: ${payload.source.status ?? "unknown"}`);
24669
+ console.log("Next: snipara-companion continue-workspace --json for editor integrations");
24670
+ }
24485
24671
  async function recallCommand(options) {
24486
24672
  ensureConfigured();
24487
24673
  const client = createClient(15e3);
@@ -30389,6 +30575,9 @@ var ADVISOR_RECEIPT_WRITE_LIMIT = 6;
30389
30575
  function normalizeStringList6(values) {
30390
30576
  return [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
30391
30577
  }
30578
+ function uniqueStrings18(values) {
30579
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
30580
+ }
30392
30581
  function isRecord17(value) {
30393
30582
  return typeof value === "object" && value !== null && !Array.isArray(value);
30394
30583
  }
@@ -30646,6 +30835,73 @@ function verificationExecutedFromEvidence(evidence) {
30646
30835
  (item) => item.command ? `${item.label}: ${item.command} (${item.status})` : `${item.label}: ${item.status}`
30647
30836
  );
30648
30837
  }
30838
+ function buildPolicyDecisionRequest(args) {
30839
+ const decision = args.run.policyGates.projectPolicyDecision;
30840
+ if (!decision || decision.verdict === "allow") return void 0;
30841
+ const changedFiles = args.run.brief.changedFiles.slice(0, 24);
30842
+ const matchedRules = decision.matchedRules.slice(0, 8);
30843
+ const options = decision.verdict === "block" ? ["respect_block", "request_exception", "mark_policy_stale"] : ["approve_once", "require_changes", "mark_policy_stale", "keep_advisory"];
30844
+ const recommendation = decision.verdict === "block" ? "respect_block" : "approve_once";
30845
+ return buildDecisionRequest({
30846
+ producer: {
30847
+ kind: "project_policy_review",
30848
+ command: "run --emit-policy-decisions",
30849
+ sourceRef: decision.receipt.receiptId
30850
+ },
30851
+ decision: `project_policy_${decision.verdict}`,
30852
+ question: decision.verdict === "block" ? "Project Policy blocked this action. Should the agent stop, request an exception, or mark the policy stale?" : "Project Policy requires human review. May the agent proceed once, change plan, or mark the policy stale?",
30853
+ evidence: {
30854
+ summary: `Project Policy verdict ${decision.verdict} for task '${args.run.brief.task ?? "unspecified"}'. Receipt ${decision.receipt.receiptId}.`,
30855
+ refs: uniqueStrings18([decision.receipt.receiptId, ...decision.receipt.ruleRefs]),
30856
+ items: matchedRules.map((rule) => ({
30857
+ ref: rule.source.ref,
30858
+ title: rule.title,
30859
+ summary: rule.requirement,
30860
+ kind: rule.source.kind,
30861
+ status: rule.strength,
30862
+ metadata: {
30863
+ ruleId: rule.id,
30864
+ scope: rule.scope,
30865
+ confidence: rule.confidence,
30866
+ reviewStatus: rule.source.reviewStatus ?? null
30867
+ }
30868
+ })),
30869
+ reasonCodes: uniqueStrings18(["project_policy_review", ...decision.reasonCodes]),
30870
+ files: changedFiles,
30871
+ applyPath: "manual_context_review",
30872
+ applyCommand: "Resolve with `snipara-companion workflow decide <request-id> --choose <option> --reviewer <name>`; if policy is stale, update or invalidate the cited decision/policy before rerunning `snipara-companion run`."
30873
+ },
30874
+ options,
30875
+ recommendation,
30876
+ rationale: "Project Policy decisions stay agent-first: the agent asks for explicit human governance and records a response receipt; no dashboard or silent policy mutation is required.",
30877
+ blocking: true,
30878
+ fingerprintParts: [
30879
+ "project_policy_review_v1",
30880
+ decision.receipt.receiptId,
30881
+ decision.verdict,
30882
+ decision.receipt.ruleRefs,
30883
+ changedFiles
30884
+ ]
30885
+ });
30886
+ }
30887
+ function emitPolicyDecisionRequests(run) {
30888
+ const requests = [buildPolicyDecisionRequest({ run })].filter(
30889
+ (request) => Boolean(request)
30890
+ );
30891
+ const writes = requests.map((request) => writeDecisionRequest(request));
30892
+ return {
30893
+ version: "project-intelligence.policy-decision-requests.v1",
30894
+ emitted: true,
30895
+ requestCount: requests.length,
30896
+ requests,
30897
+ writes,
30898
+ caveats: [
30899
+ "Policy decision requests never apply policy changes automatically.",
30900
+ "Resolve them with workflow decide so the human choice is recorded as a local response receipt.",
30901
+ "Marking a policy stale is a governance signal; the cited memory or policy still needs explicit update or invalidation."
30902
+ ]
30903
+ };
30904
+ }
30649
30905
  async function recordFirstPartyAdvisorReceipts(args) {
30650
30906
  const allRecommendations = args.judgmentCard.advisorRecommendations;
30651
30907
  if (args.options.skipAdvisorReceipts) {
@@ -30937,7 +31193,7 @@ async function buildProjectIntelligenceRun(options) {
30937
31193
  ...!options.skipPackageReview && packageReview?.status !== "ok" ? [packageReviewCommand3()] : []
30938
31194
  ] : []
30939
31195
  ];
30940
- return {
31196
+ const result = {
30941
31197
  version: "project-intelligence.production-run.v1",
30942
31198
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30943
31199
  release: Boolean(options.release),
@@ -30950,6 +31206,10 @@ async function buildProjectIntelligenceRun(options) {
30950
31206
  judgmentCard,
30951
31207
  suggestedCommands: [...new Set(suggestedCommands3)]
30952
31208
  };
31209
+ if (options.emitPolicyDecisions) {
31210
+ result.policyDecisionRequests = emitPolicyDecisionRequests(result);
31211
+ }
31212
+ return result;
30953
31213
  }
30954
31214
  async function projectIntelligenceRunCommand(options) {
30955
31215
  const result = await buildProjectIntelligenceRun(options);
@@ -33118,6 +33378,9 @@ program.command("lead-plan").description("Create a fail-closed Companion Enginee
33118
33378
  program.command("run").description("Run the production Project Intelligence judgment flow for a task or release").option("--task <task>", "Current task or change summary").option("--branch <branch>", "Branch to scope continuity signals").option("--changed-files <changedFiles...>", "Changed files to analyze").option("--recent-files <recentFiles...>", "Recently touched files for continuity lookup").option("--diff-summary <diffSummary>", "Natural-language summary for code impact").option("--max-tokens <number>", "Resume context token budget", "4000").option("--release", "Run release-oriented guard and package surface review").option("--skip-impact", "Do not run companion code impact").option("--skip-memory-health", "Do not call snipara_memory_health").option("--skip-guard", "Skip collaboration guard during release runs").option("--skip-package-review", "Skip npm package surface review").option("--served-judgment-id <id>", "Served judgment id to use for first-party advisor receipts").option("--skip-advisor-receipts", "Skip first-party advisor influence receipt capture").option(
33119
33379
  "--outcome-receipts <files...>",
33120
33380
  "Outcome Intelligence V0 receipt JSON files for local calibration"
33381
+ ).option(
33382
+ "--emit-policy-decisions",
33383
+ "Write local Decision Requests for Project Policy review/block findings"
33121
33384
  ).option("--json", "Print raw JSON").action(async (options) => {
33122
33385
  await projectIntelligenceRunCommand({
33123
33386
  task: options.task,
@@ -33134,6 +33397,7 @@ program.command("run").description("Run the production Project Intelligence judg
33134
33397
  servedJudgmentId: options.servedJudgmentId,
33135
33398
  skipAdvisorReceipts: Boolean(options.skipAdvisorReceipts),
33136
33399
  outcomeReceiptFiles: options.outcomeReceipts,
33400
+ emitPolicyDecisions: Boolean(options.emitPolicyDecisions),
33137
33401
  json: Boolean(options.json)
33138
33402
  });
33139
33403
  });
@@ -34867,6 +35131,19 @@ program.command("session-bootstrap").description(
34867
35131
  json: options.json
34868
35132
  });
34869
35133
  });
35134
+ program.command("continue-workspace").description("Print the stable Companion Continuity Contract for editor integrations").option("--max-critical-tokens <number>", "Durable memory token budget").option(
35135
+ "--include-session-context",
35136
+ "Include short-lived session carryover in addition to durable memory"
35137
+ ).option("--max-context-tokens <number>", "Short-lived session context token budget").option("--json", "Print raw JSON").action(async (options) => {
35138
+ const maxCriticalTokens = options.maxCriticalTokens !== void 0 ? parseInt(options.maxCriticalTokens, 10) : void 0;
35139
+ const maxContextTokens = options.maxContextTokens !== void 0 ? parseInt(options.maxContextTokens, 10) : void 0;
35140
+ await continueWorkspaceCommand({
35141
+ maxCriticalTokens,
35142
+ maxContextTokens,
35143
+ includeSessionContext: Boolean(options.includeSessionContext || options.maxContextTokens),
35144
+ json: options.json
35145
+ });
35146
+ });
34870
35147
  program.command("task-commit").description("Persist durable outcomes after meaningful task work, not every git commit").requiredOption("-s, --summary <summary>", "Task summary").option("-c, --category <category>", "Category").option("-o, --outcome <outcome>", "Outcome", "completed").option("-f, --files <files...>", "Files touched").option("--json", "Print raw JSON").action(async (options) => {
34871
35148
  await taskCommitCommand({
34872
35149
  summary: options.summary,
@@ -21,6 +21,35 @@ snipara-companion code impact --changed-files src/app.ts --diff-summary "next ed
21
21
  snipara-companion task-commit --summary "completed durable change" --files src/app.ts
22
22
  ```
23
23
 
24
+ ## Companion Continuity Contract V1
25
+
26
+ For editor integrations and "continue this workspace" flows, use the stable JSON
27
+ contract instead of rebuilding Companion internals:
28
+
29
+ ```bash
30
+ snipara-companion continue-workspace --include-session-context --json
31
+ ```
32
+
33
+ The response version is `snipara.companion.continuity.v1`. It includes:
34
+
35
+ - `project`: configured project id, API URL, and session id when available.
36
+ - `bootstrap` and `bootstrapQuality`: pushed session memories plus token,
37
+ freshness, and warning metadata.
38
+ - `workflow`: active workflow id, goal, current phase, phase statuses, and local
39
+ state path.
40
+ - `teamSync`: active/stale/completed counts and latest handoff summary.
41
+ - `source`: passive `.snipara/source/latest.json` status; no source sync is
42
+ performed by this command.
43
+ - `sessionSnapshot`: latest local activity summary, risks, touched files, and
44
+ recommended next action.
45
+ - `artifacts` and `nextActions`: stable local paths and commands an editor can
46
+ surface to the user.
47
+
48
+ This contract starts after `create-snipara` has activated the workspace. Editor
49
+ extensions should orchestrate these commands and render the payload, not fork
50
+ local source scanning, First Work Brief, memory candidate, or Hosted MCP config
51
+ logic.
52
+
24
53
  In this repository, the source currently lives in `packages/cli`, and the installed executable is `snipara-companion`.
25
54
 
26
55
  This package complements `snipara-mcp`. It does not replace it.
@@ -259,6 +288,13 @@ snipara-companion workflow resume --include-session-context
259
288
  path declared. Repeated resolved receipts with the same human choice and
260
289
  rationale can emit a review-only policy suggestion decision request; the
261
290
  suggestion still has a manual apply path and is never auto-applied.
291
+ - `run --emit-policy-decisions` keeps Project Policy administration agent-first:
292
+ when reviewed project policy evidence produces a `require_review` or `block`
293
+ verdict, Companion writes a local `project_policy_review` Decision Request.
294
+ The human resolves it with `workflow decide`; choices such as approving once,
295
+ requiring changes, respecting a block, requesting an exception, or marking
296
+ policy stale are recorded as receipts. No policy is edited or invalidated
297
+ automatically.
262
298
  - `memory reviews --scope project --emit-decisions` reads hosted memory review
263
299
  queue, clean-candidate, and duplicate-candidate surfaces, summarizes each item,
264
300
  and writes local Decision Request artifacts without mutating hosted memory.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.13",
3
+ "version": "3.2.15",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {