snipara-companion 3.2.2 → 3.2.3

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
@@ -71,6 +71,7 @@ npx -y snipara-companion workflow producer-triage
71
71
  npx -y snipara-companion workflow decisions
72
72
  npx -y snipara-companion workflow decide decision-abc123 --choose accept_all --reviewer alice
73
73
  npx -y snipara-companion workflow timeline
74
+ npx -y snipara-companion workflow timeline --export md
74
75
  npx -y snipara-companion workflow session --json
75
76
  npx -y snipara-companion workflow producer-report
76
77
  npx -y snipara-companion workflow producer-review --latest --outcome useful --reviewer alice
@@ -84,8 +85,14 @@ verification hints.
84
85
  `workflow timeline` reads the append-only activity log at
85
86
  `.snipara/activity/timeline.jsonl`. `workflow session` derives
86
87
  `.snipara/activity/session.json` for fast local resume and Orchestrator dogfood;
87
- Session Snapshot V0 is observational and keeps `hardRoutingAllowed=false` until
88
- explicit routing policy and receipts exist.
88
+ Session Snapshot V0 includes latest activity, risk reasons, touched files, a
89
+ next action, and advisory Intent Detection V0. Intent Detection V0 reports the
90
+ inferred intent, confidence, reason-code signals, local evidence counts, and a
91
+ suggested workflow mode hint for the human or agent to consider. It is
92
+ observational and keeps `hardRoutingAllowed=false` until explicit routing policy
93
+ and receipts exist.
94
+ `workflow timeline --export md` prints a compact redacted Markdown timeline for
95
+ handoff or publication.
89
96
 
90
97
  Workflow `phase-commit` and `final-commit` also emit Producer Loop artifacts
91
98
  under `.snipara/producer-loop/`. These are local review evidence backed by the
@@ -116,7 +123,9 @@ Other producers such as `outcome-capture preview --emit-decisions`,
116
123
  paths declared; they do not write canonical memory directly. `memory reviews`
117
124
  is the hosted-memory review connector: it reads review queue, cleanup, and
118
125
  duplicate candidate surfaces, summarizes the items for the LLM, and only writes
119
- local Decision Requests when `--emit-decisions` is passed.
126
+ local Decision Requests when `--emit-decisions` is passed. Its JSON output
127
+ includes `emittedCount`, `emittedRequestIds`, and an `emitted` summary so an
128
+ agent can continue without re-listing pending requests.
120
129
 
121
130
  ## Local First, Hosted When Useful
122
131
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary } from '@snipara/project-intelligence-contracts';
2
+ import { ProjectIntentDetectionResult, ProjectRealityCheckResult, ProjectIntelligenceEngineeringLeadPlanSummary } from '@snipara/project-intelligence-contracts';
3
3
 
4
4
  declare function resolveQueryFromToolInput(toolInput?: string, tool?: string): string | null;
5
5
 
@@ -1563,6 +1563,16 @@ interface SessionSnapshot {
1563
1563
  handoffCount: number;
1564
1564
  latestHandoffAt?: string;
1565
1565
  };
1566
+ summary: {
1567
+ latestActivityAt?: string;
1568
+ latestActivityTitle?: string;
1569
+ latestActivityKind?: string;
1570
+ risk: "none" | "watch" | "risk";
1571
+ riskReasons: string[];
1572
+ touchedFiles: string[];
1573
+ recommendedNextAction: string;
1574
+ };
1575
+ intentDetection: ProjectIntentDetectionResult;
1566
1576
  routing: {
1567
1577
  hardRoutingAllowed: false;
1568
1578
  reason: string;
@@ -1600,6 +1610,7 @@ declare function writeSessionSnapshot(options?: {
1600
1610
  cwd?: string;
1601
1611
  limit?: number;
1602
1612
  }): SessionSnapshot;
1613
+ declare function readSessionSnapshot(cwd?: string): SessionSnapshot | null;
1603
1614
 
1604
1615
  type LocalCodeOverlayMode = "working_tree" | "local_commit";
1605
1616
  type LocalCodeOverlayKind = "none" | "local_commit" | "working_tree" | "mixed";
@@ -2017,6 +2028,7 @@ interface ProjectIntelligenceBrief {
2017
2028
  memoryHealth?: Record<string, unknown>;
2018
2029
  codeImpact?: Record<string, unknown>;
2019
2030
  codeImpactSourceSelection?: CodeGraphSourceSelection;
2031
+ localSessionSnapshot?: SessionSnapshot;
2020
2032
  verificationPlan?: VerificationPlan;
2021
2033
  judgmentCard?: ProjectIntelligenceJudgmentCard;
2022
2034
  errors: Array<{
@@ -4212,4 +4224,4 @@ declare function buildOrchestratorHandoff(options: OrchestratorHandoffOptions):
4212
4224
  declare function buildAdaptiveWorkRoutingRecommendation(options: AdaptiveWorkRoutingOptions): AdaptiveWorkRoutingRecommendation;
4213
4225
  declare function writeOrchestratorHandoff(options: OrchestratorHandoffOptions): WrittenOrchestratorHandoff;
4214
4226
 
4215
- export { AUTOMATION_MANIFEST_RELATIVE_PATH, AutomationInstallConflictError, AutomationUnsupportedHookBundleError, CODING_INTELLIGENCE_LEDGER_VERSION, COLLABORATION_STATE_RELATIVE_PATH, ENGINEERING_LEAD_EXECUTION_RECEIPT_STAGES, ENGINEERING_LEAD_EXECUTION_RECEIPT_STATUSES, ENGINEERING_LEAD_POSTURES, ENGINEERING_LEAD_PROOF_VERIFICATION_SOURCES, ENGINEERING_LEAD_PROOF_VERIFICATION_STATUSES, ENGINEERING_LEAD_ROUTING_MODES, ENGINEERING_LEAD_STATUSES, ENGINEERING_LEAD_SUPERVISION_STATUSES, ENGINEERING_LEAD_WORKER_ROLES, ENGINEERING_LEAD_WORK_PACKAGE_STATUSES, ORCHESTRATOR_HANDOFF_RELATIVE_PATH, PRODUCER_LOOP_ARTIFACT_VERSION, PRODUCER_LOOP_RELATIVE_DIR, PRODUCER_LOOP_REPORT_VERSION, TEAM_SYNC_STATE_RELATIVE_PATH, WHY_OUTCOME_CAPTURE_VERSION, WORKFLOW_PLANS_RELATIVE_DIR, WORKFLOW_STATE_RELATIVE_PATH, addLocalWorker, agentReadinessAuditCommand, appendActivityEvent, archiveInactiveTeamSyncWork, attachLocalContextPackReceipts, autoArchiveTeamSyncState, buildAdaptiveWorkRoutingRecommendation, buildAgentReadinessAuditReport, buildAgenticHandoffMarkdown, buildAgenticTimeline, buildAgenticWorkStatus, buildAutomationInstallPlan, buildCanonicalEvent, buildCodeHooksInstallPlan, buildCodePromotionResult, buildCodeStatusResult, buildCodeSyncResult, buildCodingIntelligenceLedger, buildCollaborationActor, buildCollaborationGuardActionCards, buildCollaborationHooksInstallPlan, buildCompanionEngineeringLeadPlanReport, buildContextPackStats, buildEvalCaseArtifact, buildGeneratedWorkflowPlanDocument, buildHostedCodeOverlayUploadPayload, buildHostedGuardPayload, buildJournalCheckpointEntry, buildLocalCallersResult, buildLocalCodeOverlay, buildLocalContextPackReceipt, buildLocalContextPackReceipts, buildLocalImpactResult, buildLocalImportsResult, buildLocalNeighborsResult, buildLocalProjectRealityCheck, buildLocalShortestPathResult, buildLocalSourceSnapshot, buildLocalSourceStatus, buildLocalSourceSyncResult, buildMemoryAudit, buildOnboardFolderManifest, buildOrchestratorHandoff, buildProducerLoopReport, buildProjectIntelligenceBrief, buildProjectIntelligenceRun, buildProjectJudgmentCard, buildSessionBootstrapQuality, buildSessionSnapshot, buildSyncDocumentsDryRun, buildTeamSyncHandoffRecord, buildTeamSyncStartWorkRecord, buildTeamSyncSummary, buildToolCallPayload, buildToolResultPayload, buildVerificationPlan, buildWhyOutcomeCaptureReport, buildWorkflowImpactGate, buildWorkflowPhaseCommitSummary, buildWorkflowPlanScaffold, categoryFromGuardTag, classifyToolResult, cleanContextPacks, codingLedgerExportCommand, collaborationIdeStatusCommand, collectAgentReadinessLocalSignals, collectSyncDocuments, collectSyncDocumentsInput, compactHostedGuardResources, compareLocalSourceSnapshots, completeTeamSyncStateFromEvidence, completeTeamSyncWorkFromEvidence, contextPackCleanCommand, contextPackPackCommand, contextPackRetrieveCommand, contextPackStatsCommand, createClient, createEmptyCollaborationState, createEmptyTeamSyncState, createLocalQueryCache, deriveLocalCollaborationResourcesFromFiles, detectReleaseSurfacesFromFiles, detectRuntimeEnvironment, evalExportCommand, evalRunCommand, evaluateProjectPolicyGates, extractCommandFromToolInput, extractFilesFromToolInput, formatAgentReadinessAuditReport, formatCompanionEngineeringLeadPlanReport, formatOrchestratorRecommendationReason, formatPolicyGateDecision, formatProjectJudgmentCard, formatStuckGuardDecision, getAutomationManifestPath, getAutomationStatus, getCollaborationStatePath, getConfigPath, getContextPackStoragePaths, getLocalCodeOverlayCachePath, getLocalCodePromotionStatePath, getLocalSourceSnapshotPath, getOrchestratorRecommendation, getPlanStepDisplayTitle, getStagedFiles, getStuckGuardInjection, getTeamSyncStatePath, ingestReferences, installAutomationBundle, leadPlanCommand, listProjectsForApiKey, loadAutomationManifest, loadCollaborationState, loadConfig, loadTeamSyncState, memoryAuditCommand, memoryCleanCandidatesCommand, memoryCompactCommand, memoryHealthCommand, memoryLocalCommand, memoryReviewsCommand, normalizeCollaborationFiles, normalizeGuardTag, normalizeWorkflowPlanInput, outcomeCapturePreviewCommand, packContext, parseCollaborationResources, projectIntelligenceBriefCommand, projectIntelligenceRunCommand, readActivityTimeline, readLocalCodeOverlayCache, readLocalCodePromotionState, readLocalSourceSnapshot, readLocalWorkersConfig, realityCheckCommand, referencesIngestCommand, referencesScanCommand, resolveContextPackRecord, resolveFullWorkflowTokenBudget, resolveLocalWorkerRoutingDefaults, resolveQueryFromToolInput, retrieveContextPack, runMemoryGuardCheck, saveCollaborationState, saveConfig, saveTeamSyncState, scanReferences, shouldSuggestOrchestratorForWorkflow, shouldSuggestRuntimeForWorkflow, summarizeLocalCodeOverlay, teamSyncSweepCommand, validatePlanResult, verifyCommand, workersLocalAddCommand, workersLocalStatusCommand, writeLocalCodeOverlayCache, writeLocalCodePromotionState, writeLocalSourceSnapshot, writeOrchestratorHandoff, writeProducerLoopArtifact, writeSessionSnapshot };
4227
+ export { AUTOMATION_MANIFEST_RELATIVE_PATH, AutomationInstallConflictError, AutomationUnsupportedHookBundleError, CODING_INTELLIGENCE_LEDGER_VERSION, COLLABORATION_STATE_RELATIVE_PATH, ENGINEERING_LEAD_EXECUTION_RECEIPT_STAGES, ENGINEERING_LEAD_EXECUTION_RECEIPT_STATUSES, ENGINEERING_LEAD_POSTURES, ENGINEERING_LEAD_PROOF_VERIFICATION_SOURCES, ENGINEERING_LEAD_PROOF_VERIFICATION_STATUSES, ENGINEERING_LEAD_ROUTING_MODES, ENGINEERING_LEAD_STATUSES, ENGINEERING_LEAD_SUPERVISION_STATUSES, ENGINEERING_LEAD_WORKER_ROLES, ENGINEERING_LEAD_WORK_PACKAGE_STATUSES, ORCHESTRATOR_HANDOFF_RELATIVE_PATH, PRODUCER_LOOP_ARTIFACT_VERSION, PRODUCER_LOOP_RELATIVE_DIR, PRODUCER_LOOP_REPORT_VERSION, TEAM_SYNC_STATE_RELATIVE_PATH, WHY_OUTCOME_CAPTURE_VERSION, WORKFLOW_PLANS_RELATIVE_DIR, WORKFLOW_STATE_RELATIVE_PATH, addLocalWorker, agentReadinessAuditCommand, appendActivityEvent, archiveInactiveTeamSyncWork, attachLocalContextPackReceipts, autoArchiveTeamSyncState, buildAdaptiveWorkRoutingRecommendation, buildAgentReadinessAuditReport, buildAgenticHandoffMarkdown, buildAgenticTimeline, buildAgenticWorkStatus, buildAutomationInstallPlan, buildCanonicalEvent, buildCodeHooksInstallPlan, buildCodePromotionResult, buildCodeStatusResult, buildCodeSyncResult, buildCodingIntelligenceLedger, buildCollaborationActor, buildCollaborationGuardActionCards, buildCollaborationHooksInstallPlan, buildCompanionEngineeringLeadPlanReport, buildContextPackStats, buildEvalCaseArtifact, buildGeneratedWorkflowPlanDocument, buildHostedCodeOverlayUploadPayload, buildHostedGuardPayload, buildJournalCheckpointEntry, buildLocalCallersResult, buildLocalCodeOverlay, buildLocalContextPackReceipt, buildLocalContextPackReceipts, buildLocalImpactResult, buildLocalImportsResult, buildLocalNeighborsResult, buildLocalProjectRealityCheck, buildLocalShortestPathResult, buildLocalSourceSnapshot, buildLocalSourceStatus, buildLocalSourceSyncResult, buildMemoryAudit, buildOnboardFolderManifest, buildOrchestratorHandoff, buildProducerLoopReport, buildProjectIntelligenceBrief, buildProjectIntelligenceRun, buildProjectJudgmentCard, buildSessionBootstrapQuality, buildSessionSnapshot, buildSyncDocumentsDryRun, buildTeamSyncHandoffRecord, buildTeamSyncStartWorkRecord, buildTeamSyncSummary, buildToolCallPayload, buildToolResultPayload, buildVerificationPlan, buildWhyOutcomeCaptureReport, buildWorkflowImpactGate, buildWorkflowPhaseCommitSummary, buildWorkflowPlanScaffold, categoryFromGuardTag, classifyToolResult, cleanContextPacks, codingLedgerExportCommand, collaborationIdeStatusCommand, collectAgentReadinessLocalSignals, collectSyncDocuments, collectSyncDocumentsInput, compactHostedGuardResources, compareLocalSourceSnapshots, completeTeamSyncStateFromEvidence, completeTeamSyncWorkFromEvidence, contextPackCleanCommand, contextPackPackCommand, contextPackRetrieveCommand, contextPackStatsCommand, createClient, createEmptyCollaborationState, createEmptyTeamSyncState, createLocalQueryCache, deriveLocalCollaborationResourcesFromFiles, detectReleaseSurfacesFromFiles, detectRuntimeEnvironment, evalExportCommand, evalRunCommand, evaluateProjectPolicyGates, extractCommandFromToolInput, extractFilesFromToolInput, formatAgentReadinessAuditReport, formatCompanionEngineeringLeadPlanReport, formatOrchestratorRecommendationReason, formatPolicyGateDecision, formatProjectJudgmentCard, formatStuckGuardDecision, getAutomationManifestPath, getAutomationStatus, getCollaborationStatePath, getConfigPath, getContextPackStoragePaths, getLocalCodeOverlayCachePath, getLocalCodePromotionStatePath, getLocalSourceSnapshotPath, getOrchestratorRecommendation, getPlanStepDisplayTitle, getStagedFiles, getStuckGuardInjection, getTeamSyncStatePath, ingestReferences, installAutomationBundle, leadPlanCommand, listProjectsForApiKey, loadAutomationManifest, loadCollaborationState, loadConfig, loadTeamSyncState, memoryAuditCommand, memoryCleanCandidatesCommand, memoryCompactCommand, memoryHealthCommand, memoryLocalCommand, memoryReviewsCommand, normalizeCollaborationFiles, normalizeGuardTag, normalizeWorkflowPlanInput, outcomeCapturePreviewCommand, packContext, parseCollaborationResources, projectIntelligenceBriefCommand, projectIntelligenceRunCommand, readActivityTimeline, readLocalCodeOverlayCache, readLocalCodePromotionState, readLocalSourceSnapshot, readLocalWorkersConfig, readSessionSnapshot, realityCheckCommand, referencesIngestCommand, referencesScanCommand, resolveContextPackRecord, resolveFullWorkflowTokenBudget, resolveLocalWorkerRoutingDefaults, resolveQueryFromToolInput, retrieveContextPack, runMemoryGuardCheck, saveCollaborationState, saveConfig, saveTeamSyncState, scanReferences, shouldSuggestOrchestratorForWorkflow, shouldSuggestRuntimeForWorkflow, summarizeLocalCodeOverlay, teamSyncSweepCommand, validatePlanResult, verifyCommand, workersLocalAddCommand, workersLocalStatusCommand, writeLocalCodeOverlayCache, writeLocalCodePromotionState, writeLocalSourceSnapshot, writeOrchestratorHandoff, writeProducerLoopArtifact, writeSessionSnapshot };
package/dist/index.js CHANGED
@@ -188,6 +188,7 @@ __export(index_exports, {
188
188
  readLocalCodePromotionState: () => readLocalCodePromotionState,
189
189
  readLocalSourceSnapshot: () => readLocalSourceSnapshot,
190
190
  readLocalWorkersConfig: () => readLocalWorkersConfig,
191
+ readSessionSnapshot: () => readSessionSnapshot,
191
192
  realityCheckCommand: () => realityCheckCommand,
192
193
  referencesIngestCommand: () => referencesIngestCommand,
193
194
  referencesScanCommand: () => referencesScanCommand,
@@ -10139,6 +10140,231 @@ function sortJsonValue(value) {
10139
10140
  return value;
10140
10141
  }
10141
10142
 
10143
+ // ../project-intelligence-contracts/src/intent-detection.ts
10144
+ var INTENT_DETECTION_VERSION = "snipara.intent_detection.v0";
10145
+ var PROJECT_INTENT_DETECTION_INTENTS = [
10146
+ "debugging",
10147
+ "implementing_feature",
10148
+ "refactoring",
10149
+ "reviewing",
10150
+ "investigating",
10151
+ "release_deploy",
10152
+ "unknown"
10153
+ ];
10154
+ var PROJECT_INTENT_DETECTION_ACTIONS = [
10155
+ "read",
10156
+ "write",
10157
+ "test",
10158
+ "build",
10159
+ "deploy",
10160
+ "review",
10161
+ "decision",
10162
+ "handoff",
10163
+ "unknown"
10164
+ ];
10165
+ var INTENT_PRIORITY = {
10166
+ release_deploy: 60,
10167
+ debugging: 50,
10168
+ implementing_feature: 40,
10169
+ refactoring: 30,
10170
+ reviewing: 20,
10171
+ investigating: 10,
10172
+ unknown: 0
10173
+ };
10174
+ function textMatches(text, patterns) {
10175
+ return patterns.some((pattern) => pattern.test(text));
10176
+ }
10177
+ function uniqueStrings5(values) {
10178
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
10179
+ }
10180
+ function compactSignals(signals) {
10181
+ const byCode = /* @__PURE__ */ new Map();
10182
+ for (const signal of signals) {
10183
+ const existing = byCode.get(signal.code);
10184
+ if (!existing || signal.weight > existing.weight) {
10185
+ byCode.set(signal.code, signal);
10186
+ }
10187
+ }
10188
+ return [...byCode.values()];
10189
+ }
10190
+ function metadataString(metadata, key) {
10191
+ const value = metadata?.[key];
10192
+ return typeof value === "string" ? value : "";
10193
+ }
10194
+ function inferAction(event) {
10195
+ const explicitAction = metadataString(event.metadata, "action");
10196
+ if (PROJECT_INTENT_DETECTION_ACTIONS.includes(explicitAction)) {
10197
+ return explicitAction;
10198
+ }
10199
+ const text = [event.kind, event.source, event.title, event.summary, event.detail, event.outcome].filter(Boolean).join(" ").toLowerCase();
10200
+ if (textMatches(text, [/\b(test|tests|spec|vitest|jest|pytest|node --test)\b/i])) return "test";
10201
+ if (textMatches(text, [/\b(build|type-?check|lint|compile)\b/i])) return "build";
10202
+ if (textMatches(text, [/\b(deploy|release|publish|rollout|production)\b/i])) return "deploy";
10203
+ if (textMatches(text, [/\b(review|audit|inspect|read|look(ed)? at|rg|grep)\b/i])) return "read";
10204
+ if (textMatches(text, [/\b(decision|decide|policy suggestion|manual apply)\b/i]))
10205
+ return "decision";
10206
+ if (textMatches(text, [/\b(handoff|resume|final-commit|phase-commit)\b/i])) return "handoff";
10207
+ if (textMatches(text, [/\b(edit|write|add(ed)?|implement|fix|refactor|change(d)?)\b/i])) {
10208
+ return "write";
10209
+ }
10210
+ return "unknown";
10211
+ }
10212
+ function normalizeEvent(event) {
10213
+ return {
10214
+ text: [event.kind, event.source, event.title, event.summary, event.detail, event.outcome].filter(Boolean).join(" ").toLowerCase(),
10215
+ files: uniqueStrings5(event.files ?? []),
10216
+ action: inferAction(event),
10217
+ outcome: event.outcome?.toLowerCase()
10218
+ };
10219
+ }
10220
+ function actionCount(events, action) {
10221
+ return events.filter((event) => event.action === action).length;
10222
+ }
10223
+ function addSignal(scores, intent, code2, weight, detail) {
10224
+ const signals = scores.get(intent) ?? [];
10225
+ signals.push({ code: code2, intent, weight, ...detail ? { detail } : {} });
10226
+ scores.set(intent, signals);
10227
+ }
10228
+ function confidenceFor(score, signalCount) {
10229
+ if (score >= 7 && signalCount >= 3) return "high";
10230
+ if (score >= 4 && signalCount >= 2) return "medium";
10231
+ return "low";
10232
+ }
10233
+ function suggestedModeFor(intent, confidence, files) {
10234
+ if (intent === "release_deploy") return "full";
10235
+ if (intent === "refactoring" && (confidence !== "low" || files.length >= 5)) return "full";
10236
+ if (intent === "debugging" && confidence === "high") return "standard";
10237
+ if (intent === "investigating" || intent === "reviewing") return "lite";
10238
+ return files.length >= 5 ? "standard" : "lite";
10239
+ }
10240
+ function buildIntentDetectionFromTimeline(events) {
10241
+ const normalized = events.map(normalizeEvent);
10242
+ const text = normalized.map((event) => event.text).join("\n");
10243
+ const files = uniqueStrings5(normalized.flatMap((event) => event.files));
10244
+ const testFiles2 = files.filter((file) => /(test|spec)\.|__tests__|tests?\//i.test(file));
10245
+ const docsFiles = files.filter((file) => /(^|\/)(docs?|README|CHANGELOG)|\.md$/i.test(file));
10246
+ const packageFiles = files.filter(
10247
+ (file) => /package\.json|CHANGELOG|README|pyproject|setup\.py|publish/i.test(file)
10248
+ );
10249
+ const sourceFiles = files.filter((file) => /\.(ts|tsx|js|jsx|py|go|rs|java|rb)$/i.test(file));
10250
+ const failureEvents = normalized.filter(
10251
+ (event) => event.outcome === "failed" || textMatches(event.text, [/\b(fail(ed|ure)?|error|timeout|retry|regression)\b/i])
10252
+ );
10253
+ const scores = /* @__PURE__ */ new Map();
10254
+ if (failureEvents.length > 0) {
10255
+ addSignal(scores, "debugging", "failure_signal", 3, `${failureEvents.length} failure event(s)`);
10256
+ }
10257
+ if (testFiles2.length > 0 && (failureEvents.length > 0 || actionCount(normalized, "test") > 0)) {
10258
+ addSignal(scores, "debugging", "test_loop_signal", 3, `${testFiles2.length} test file(s)`);
10259
+ }
10260
+ if (failureEvents.length > 0 && actionCount(normalized, "write") > 0 && files.some((file) => failureEvents.some((event) => event.files.includes(file)))) {
10261
+ addSignal(scores, "debugging", "same_file_edited_after_failure", 2);
10262
+ }
10263
+ if (textMatches(text, [/\b(deploy|deployment|release|publish|production|rollout)\b/i])) {
10264
+ addSignal(scores, "release_deploy", "release_or_deploy_terms", 3);
10265
+ }
10266
+ if (packageFiles.length > 0) {
10267
+ addSignal(scores, "release_deploy", "package_or_release_files", 2);
10268
+ }
10269
+ if (docsFiles.length > 0 && packageFiles.length > 0) {
10270
+ addSignal(scores, "release_deploy", "docs_plus_package_surface", 2);
10271
+ }
10272
+ if (actionCount(normalized, "deploy") > 0) {
10273
+ addSignal(scores, "release_deploy", "deploy_action", 3);
10274
+ }
10275
+ if (textMatches(text, [/\b(feature|implement(ed|ing)?|add(ed|s)?|new behavior|ship)\b/i])) {
10276
+ addSignal(scores, "implementing_feature", "feature_language", 3);
10277
+ }
10278
+ if (testFiles2.length > 0 && sourceFiles.length > 0) {
10279
+ addSignal(scores, "implementing_feature", "tests_plus_product_files", 2);
10280
+ }
10281
+ if (actionCount(normalized, "write") > 0 && sourceFiles.length > 0) {
10282
+ addSignal(scores, "implementing_feature", "source_write_signal", 2);
10283
+ }
10284
+ if (textMatches(text, [/\b(refactor|restructure|cleanup|rename|dedupe|simplif(y|ied))\b/i])) {
10285
+ addSignal(scores, "refactoring", "refactor_language", 3);
10286
+ }
10287
+ if (files.length >= 5 && !textMatches(text, [/\b(feature|deploy|release|publish)\b/i])) {
10288
+ addSignal(scores, "refactoring", "broad_change_without_feature_or_release_terms", 2);
10289
+ }
10290
+ if (sourceFiles.length >= 4 && testFiles2.length === 0) {
10291
+ addSignal(scores, "refactoring", "many_source_files_without_tests", 1);
10292
+ }
10293
+ if (textMatches(text, [/\b(review|audit|inspect|triage|assessment|verdict)\b/i])) {
10294
+ addSignal(scores, "reviewing", "review_language", 3);
10295
+ }
10296
+ if (actionCount(normalized, "decision") > 0) {
10297
+ addSignal(scores, "reviewing", "decision_review_signal", 2);
10298
+ }
10299
+ if (textMatches(text, [/\b(policy suggestion|manual apply|required|reject|approve)\b/i])) {
10300
+ addSignal(scores, "reviewing", "human_decision_signal", 2);
10301
+ }
10302
+ if (textMatches(text, [/\b(investigat(e|ing)|look into|find out|read|rg|grep|search)\b/i])) {
10303
+ addSignal(scores, "investigating", "investigation_language", 3);
10304
+ }
10305
+ if (actionCount(normalized, "read") >= 2 && actionCount(normalized, "write") === 0) {
10306
+ addSignal(scores, "investigating", "read_heavy_no_write_signal", 2);
10307
+ }
10308
+ if (events.length > 0 && files.length === 0) {
10309
+ addSignal(scores, "investigating", "activity_without_file_changes", 1);
10310
+ }
10311
+ const ranked = PROJECT_INTENT_DETECTION_INTENTS.map((intent2) => {
10312
+ const signals = compactSignals(scores.get(intent2) ?? []);
10313
+ return {
10314
+ intent: intent2,
10315
+ signals,
10316
+ score: signals.reduce((sum, signal) => sum + signal.weight, 0)
10317
+ };
10318
+ }).sort((left, right) => {
10319
+ if (right.score !== left.score) return right.score - left.score;
10320
+ return INTENT_PRIORITY[right.intent] - INTENT_PRIORITY[left.intent];
10321
+ });
10322
+ const best = ranked[0];
10323
+ const intent = best.score > 0 ? best.intent : "unknown";
10324
+ const signalDetails = best.score > 0 ? best.signals : [{ code: "insufficient_timeline_signals", intent: "unknown", weight: 0 }];
10325
+ const confidence = confidenceFor(best.score, signalDetails.length);
10326
+ const actionCounts = PROJECT_INTENT_DETECTION_ACTIONS.reduce((counts, action) => {
10327
+ const count = actionCount(normalized, action);
10328
+ if (count > 0) counts[action] = count;
10329
+ return counts;
10330
+ }, {});
10331
+ const suggestedWorkflowMode = suggestedModeFor(intent, confidence, files);
10332
+ const reasonCodes = uniqueStrings5([
10333
+ ...signalDetails.map((signal) => signal.code),
10334
+ `intent_${intent}`,
10335
+ `confidence_${confidence}`
10336
+ ]);
10337
+ return {
10338
+ version: INTENT_DETECTION_VERSION,
10339
+ intent,
10340
+ confidence,
10341
+ signals: signalDetails.map((signal) => signal.code),
10342
+ signalDetails,
10343
+ reasonCodes,
10344
+ evidence: {
10345
+ eventCount: events.length,
10346
+ fileCount: files.length,
10347
+ uniqueTouchedFiles: files,
10348
+ actionCounts
10349
+ },
10350
+ advisoryRouting: {
10351
+ hardRoutingAllowed: false,
10352
+ suggestedWorkflowMode,
10353
+ reasonCodes,
10354
+ caveats: [
10355
+ "Suggested workflow mode is advisory only.",
10356
+ "Explicit user choice, policy, and receipts are required before routing work."
10357
+ ]
10358
+ },
10359
+ hardRoutingAllowed: false,
10360
+ caveats: [
10361
+ "Intent Detection V0 is advisory only.",
10362
+ "It must not trigger workers, merges, canonical memory writes, or blocking gates by itself.",
10363
+ "Confidence is heuristic and derived from local timeline evidence."
10364
+ ]
10365
+ };
10366
+ }
10367
+
10142
10368
  // src/commands/decision-requests.ts
10143
10369
  var fs12 = __toESM(require("fs"));
10144
10370
  var path11 = __toESM(require("path"));
@@ -10469,7 +10695,7 @@ function memoryEvidenceItem(value, options) {
10469
10695
  summary: options.summary,
10470
10696
  kind: options.kind,
10471
10697
  status: options.status,
10472
- files: uniqueStrings5(evidenceRefs),
10698
+ files: uniqueStrings6(evidenceRefs),
10473
10699
  metadata: compactObject({
10474
10700
  source: options.source,
10475
10701
  bucket: options.bucket,
@@ -10510,7 +10736,7 @@ function reviewQueueItems(result) {
10510
10736
  action: "review_queue_item",
10511
10737
  options: ["accept", "reject", "archive", "invalidate", "keep_pending"],
10512
10738
  recommendation: reviewStatus === "approved" || status === "active" ? "keep_pending" : "accept",
10513
- reasonCodes: uniqueStrings5(["memory_review_queue", reason ?? "", status ?? ""]),
10739
+ reasonCodes: uniqueStrings6(["memory_review_queue", reason ?? "", status ?? ""]),
10514
10740
  evidenceItem: memoryEvidenceItem(item, {
10515
10741
  ref: `memory:${memoryId}`,
10516
10742
  title: `Review memory ${memoryId}`,
@@ -10549,7 +10775,7 @@ function cleanCandidateItems(result) {
10549
10775
  action: `${bucket}_candidate`,
10550
10776
  options,
10551
10777
  recommendation,
10552
- reasonCodes: uniqueStrings5(["memory_clean_candidates", bucket, reason]),
10778
+ reasonCodes: uniqueStrings6(["memory_clean_candidates", bucket, reason]),
10553
10779
  evidenceItem: memoryEvidenceItem(candidate, {
10554
10780
  ref: `memory:${memoryId}`,
10555
10781
  title: `${bucket.replace(/_/g, " ")} memory candidate ${memoryId}`,
@@ -10586,7 +10812,7 @@ function duplicateCandidateItems(result) {
10586
10812
  action: "duplicate_candidate",
10587
10813
  options: ["merge", "supersede", "keep", "inspect"],
10588
10814
  recommendation: keepMemoryId ? "supersede" : "inspect",
10589
- reasonCodes: uniqueStrings5(["memory_duplicate_candidates", `group_${index + 1}`]),
10815
+ reasonCodes: uniqueStrings6(["memory_duplicate_candidates", `group_${index + 1}`]),
10590
10816
  evidenceItem: memoryEvidenceItem(candidate, {
10591
10817
  ref: `memory:${memoryId}`,
10592
10818
  title: `Duplicate memory candidate ${memoryId}`,
@@ -10645,6 +10871,9 @@ async function buildMemoryReviewConnector(options) {
10645
10871
  items: [],
10646
10872
  requests: [],
10647
10873
  writes: [],
10874
+ emitted: { enabled: Boolean(options.emitDecisions), count: 0, requestIds: [] },
10875
+ emittedCount: 0,
10876
+ emittedRequestIds: [],
10648
10877
  caveats: [
10649
10878
  "Read-only connector: hosted memory tools are queried, but no memory is mutated.",
10650
10879
  "Emitted decision requests must still be resolved explicitly; no timeout/default applies.",
@@ -10711,6 +10940,13 @@ async function buildMemoryReviewConnector(options) {
10711
10940
  result.requests = result.items.map(buildMemoryReviewDecisionRequest);
10712
10941
  if (options.emitDecisions) {
10713
10942
  result.writes = result.requests.map((request) => writeDecisionRequest(request));
10943
+ result.emittedRequestIds = result.writes.map((write) => write.requestId);
10944
+ result.emittedCount = result.emittedRequestIds.length;
10945
+ result.emitted = {
10946
+ enabled: true,
10947
+ count: result.emittedCount,
10948
+ requestIds: result.emittedRequestIds
10949
+ };
10714
10950
  }
10715
10951
  return result;
10716
10952
  }
@@ -10806,10 +11042,10 @@ function buildMemoryAuditSummary(audit) {
10806
11042
  cleanupCandidateCounts,
10807
11043
  ...typeof compactDryRun.mutated === "boolean" ? { compactDryRunMutated: compactDryRun.mutated } : {},
10808
11044
  ...compactDryRunPlannedActions !== void 0 ? { compactDryRunPlannedActions } : {},
10809
- recommendedActions: uniqueStrings5(recommendedActions)
11045
+ recommendedActions: uniqueStrings6(recommendedActions)
10810
11046
  };
10811
11047
  }
10812
- function uniqueStrings5(values) {
11048
+ function uniqueStrings6(values) {
10813
11049
  return Array.from(new Set(values));
10814
11050
  }
10815
11051
  function printMemoryAuditSummary(summary) {
@@ -11024,7 +11260,7 @@ async function memoryAuditCommand(options) {
11024
11260
  var fs13 = __toESM(require("fs"));
11025
11261
  var path12 = __toESM(require("path"));
11026
11262
  var import_node_child_process2 = require("child_process");
11027
- function uniqueStrings6(values) {
11263
+ function uniqueStrings7(values) {
11028
11264
  return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
11029
11265
  }
11030
11266
  function slugify(value, fallback) {
@@ -11032,7 +11268,7 @@ function slugify(value, fallback) {
11032
11268
  return slug2 || fallback;
11033
11269
  }
11034
11270
  function keywordHints(value) {
11035
- return uniqueStrings6(
11271
+ return uniqueStrings7(
11036
11272
  value.split(/[^A-Za-z0-9_.:/-]+/).map((part) => part.trim()).filter((part) => part.length >= 4).slice(0, 8)
11037
11273
  );
11038
11274
  }
@@ -11078,17 +11314,17 @@ function expectedItems(section, values, files) {
11078
11314
  }
11079
11315
  function buildEvalCaseArtifact(options = {}) {
11080
11316
  const rootDir = path12.resolve(options.dir ?? process.cwd());
11081
- const files = uniqueStrings6(options.files);
11082
- const commandsRun = uniqueStrings6(options.commandRun);
11317
+ const files = uniqueStrings7(options.files);
11318
+ const commandsRun = uniqueStrings7(options.commandRun);
11083
11319
  const summary = options.summary?.trim() || "Snipara companion local workflow evaluation";
11084
11320
  const id = options.id?.trim() || slugify(summary, "snipara-companion-eval");
11085
11321
  const expected = {};
11086
11322
  const sectionInputs = {
11087
- context: uniqueStrings6(options.context),
11088
- decisions: uniqueStrings6(options.decision),
11089
- impact: uniqueStrings6(options.impact),
11090
- verification: uniqueStrings6(options.verification),
11091
- continuity: uniqueStrings6(options.continuity)
11323
+ context: uniqueStrings7(options.context),
11324
+ decisions: uniqueStrings7(options.decision),
11325
+ impact: uniqueStrings7(options.impact),
11326
+ verification: uniqueStrings7(options.verification),
11327
+ continuity: uniqueStrings7(options.continuity)
11092
11328
  };
11093
11329
  for (const [section, values] of Object.entries(sectionInputs)) {
11094
11330
  if (values.length > 0) {
@@ -12550,7 +12786,7 @@ var SESSION_SNAPSHOT_VERSION = "snipara.session_snapshot.v0";
12550
12786
  var ACTIVITY_RELATIVE_DIR = path16.join(".snipara", "activity");
12551
12787
  var ACTIVITY_TIMELINE_RELATIVE_PATH = path16.join(ACTIVITY_RELATIVE_DIR, "timeline.jsonl");
12552
12788
  var SESSION_SNAPSHOT_RELATIVE_PATH = path16.join(ACTIVITY_RELATIVE_DIR, "session.json");
12553
- function uniqueStrings7(values) {
12789
+ function uniqueStrings8(values) {
12554
12790
  return Array.from(new Set(values.map((value) => value?.trim()).filter(Boolean)));
12555
12791
  }
12556
12792
  function stableJson(value) {
@@ -12623,8 +12859,8 @@ function appendActivityEvent(input) {
12623
12859
  ...input.phaseId ? { phaseId: input.phaseId } : {},
12624
12860
  ...input.actor ? { actor: input.actor } : {},
12625
12861
  ...input.outcome ? { outcome: input.outcome } : {},
12626
- files: uniqueStrings7(input.files ?? []),
12627
- refs: uniqueStrings7(input.refs ?? []),
12862
+ files: uniqueStrings8(input.files ?? []),
12863
+ refs: uniqueStrings8(input.refs ?? []),
12628
12864
  metadata: input.metadata ?? {}
12629
12865
  };
12630
12866
  const timelinePath = getActivityTimelinePath(cwd);
@@ -12705,6 +12941,37 @@ function teamSyncSnapshot(cwd) {
12705
12941
  ...latestHandoffAt ? { latestHandoffAt } : {}
12706
12942
  };
12707
12943
  }
12944
+ function deriveSessionSnapshotSummary(args) {
12945
+ const riskReasons = [];
12946
+ if (args.pendingDecisionCount > 0) {
12947
+ riskReasons.push(`${args.pendingDecisionCount} pending decision request(s)`);
12948
+ }
12949
+ if (args.producerLoop.unreviewedCount > 0) {
12950
+ riskReasons.push(`${args.producerLoop.unreviewedCount} unreviewed Producer Loop artifact(s)`);
12951
+ }
12952
+ if (args.teamSync.activeWorkCount > 0) {
12953
+ riskReasons.push(`${args.teamSync.activeWorkCount} active Team Sync work item(s)`);
12954
+ }
12955
+ if (args.workflow?.status === "blocked") {
12956
+ riskReasons.push("workflow is blocked");
12957
+ }
12958
+ const touchedFiles = uniqueStrings8(args.latestEvents.flatMap((event) => event.files)).slice(
12959
+ 0,
12960
+ 20
12961
+ );
12962
+ const latestActivity = args.latestEvents[0];
12963
+ const risk = riskReasons.length >= 2 ? "risk" : riskReasons.length === 1 ? "watch" : "none";
12964
+ const recommendedNextAction = args.workflow?.status === "completed" ? "Start the next managed workflow or export the timeline for handoff." : args.workflow?.currentPhaseId ? `Continue phase ${args.workflow.currentPhaseId} or commit it when verified.` : args.pendingDecisionCount > 0 ? "Resolve pending decision requests before applying follow-up actions." : "Review the latest activity and run the next verification command.";
12965
+ return {
12966
+ ...latestActivity?.timestamp ? { latestActivityAt: latestActivity.timestamp } : {},
12967
+ ...latestActivity?.title ? { latestActivityTitle: latestActivity.title } : {},
12968
+ ...latestActivity?.kind ? { latestActivityKind: latestActivity.kind } : {},
12969
+ risk,
12970
+ riskReasons,
12971
+ touchedFiles,
12972
+ recommendedNextAction
12973
+ };
12974
+ }
12708
12975
  function buildSessionSnapshot(options = {}) {
12709
12976
  const startedAt = Date.now();
12710
12977
  const cwd = options.cwd ?? process.cwd();
@@ -12712,6 +12979,9 @@ function buildSessionSnapshot(options = {}) {
12712
12979
  const latestEvents = events.slice(0, options.limit && options.limit > 0 ? options.limit : 20);
12713
12980
  const pendingDecisions = readJsonDir(path16.join(cwd, ".snipara", "decisions", "pending"));
12714
12981
  const resolvedDecisions = readJsonDir(path16.join(cwd, ".snipara", "decisions", "resolved"));
12982
+ const workflow2 = workflowSnapshot(cwd);
12983
+ const producerLoop = producerLoopSummary(cwd);
12984
+ const teamSync2 = teamSyncSnapshot(cwd);
12715
12985
  const snapshot = {
12716
12986
  schemaVersion: SESSION_SNAPSHOT_VERSION,
12717
12987
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -12720,7 +12990,7 @@ function buildSessionSnapshot(options = {}) {
12720
12990
  timelinePath: getActivityTimelinePath(cwd),
12721
12991
  snapshotPath: getSessionSnapshotPath(cwd)
12722
12992
  },
12723
- workflow: workflowSnapshot(cwd),
12993
+ workflow: workflow2,
12724
12994
  activity: {
12725
12995
  totalEvents: events.length,
12726
12996
  ...events[0]?.timestamp ? { latestEventAt: events[0].timestamp } : {},
@@ -12733,8 +13003,26 @@ function buildSessionSnapshot(options = {}) {
12733
13003
  resolvedCount: resolvedDecisions.length,
12734
13004
  recurringResolvedFingerprintCount: countDecisionFingerprintRepeats(resolvedDecisions)
12735
13005
  },
12736
- producerLoop: producerLoopSummary(cwd),
12737
- teamSync: teamSyncSnapshot(cwd),
13006
+ producerLoop,
13007
+ teamSync: teamSync2,
13008
+ summary: deriveSessionSnapshotSummary({
13009
+ workflow: workflow2,
13010
+ latestEvents,
13011
+ pendingDecisionCount: pendingDecisions.length,
13012
+ producerLoop,
13013
+ teamSync: teamSync2
13014
+ }),
13015
+ intentDetection: buildIntentDetectionFromTimeline(
13016
+ latestEvents.map((event) => ({
13017
+ kind: event.kind,
13018
+ source: event.source,
13019
+ title: event.title,
13020
+ summary: event.summary,
13021
+ outcome: event.outcome,
13022
+ files: event.files,
13023
+ metadata: event.metadata
13024
+ }))
13025
+ ),
12738
13026
  routing: {
12739
13027
  hardRoutingAllowed: false,
12740
13028
  reason: "Session Snapshot V0 is observational only; hard routing needs explicit policy and receipt gates."
@@ -12758,9 +13046,16 @@ function writeSessionSnapshot(options = {}) {
12758
13046
  `, "utf8");
12759
13047
  return snapshot;
12760
13048
  }
13049
+ function readSessionSnapshot(cwd = process.cwd()) {
13050
+ const parsed = safeReadJson(getSessionSnapshotPath(cwd));
13051
+ if (parsed?.schemaVersion === SESSION_SNAPSHOT_VERSION && parsed.summary && parsed.intentDetection) {
13052
+ return parsed;
13053
+ }
13054
+ return null;
13055
+ }
12761
13056
 
12762
13057
  // src/commands/journal.ts
12763
- function uniqueStrings8(values) {
13058
+ function uniqueStrings9(values) {
12764
13059
  if (!values) {
12765
13060
  return [];
12766
13061
  }
@@ -12788,7 +13083,7 @@ function buildJournalCheckpointEntry(payload) {
12788
13083
  payload.actor ? `Actor: ${payload.actor}` : null,
12789
13084
  payload.attention ? `Attention: ${payload.attention}` : null,
12790
13085
  payload.next ? `Next: ${payload.next}` : null,
12791
- uniqueStrings8(payload.files).length > 0 ? `Files: ${uniqueStrings8(payload.files).join(", ")}` : null
13086
+ uniqueStrings9(payload.files).length > 0 ? `Files: ${uniqueStrings9(payload.files).join(", ")}` : null
12792
13087
  ].filter((line) => Boolean(line));
12793
13088
  return {
12794
13089
  text: lines.join("\n"),
@@ -12884,7 +13179,7 @@ function createRedactor(rootDir) {
12884
13179
  patterns.add(item.name);
12885
13180
  item.pattern.lastIndex = 0;
12886
13181
  }
12887
- const uniqueMatchedPatterns = uniqueStrings9(matchedPatterns);
13182
+ const uniqueMatchedPatterns = uniqueStrings10(matchedPatterns);
12888
13183
  if (uniqueMatchedPatterns.length > 0) {
12889
13184
  redactedValueCount += 1;
12890
13185
  }
@@ -12999,14 +13294,14 @@ function buildRepoState(input, options, rootDir, redactor) {
12999
13294
  const inputRepo = inputRecord(input, "repoState", "repo_state", "repo");
13000
13295
  const detected = detectGitState(rootDir);
13001
13296
  const statusCounts = countGitStatus(detected.statusLines);
13002
- const changedFiles = uniqueStrings9([
13297
+ const changedFiles = uniqueStrings10([
13003
13298
  ...normalizeUnknownStringList(inputRepo.changedFiles),
13004
13299
  ...normalizeUnknownStringList(inputRepo.changed_files),
13005
13300
  ...normalizeUnknownStringList(input.changedFiles),
13006
13301
  ...normalizeUnknownStringList(input.changed_files),
13007
13302
  ...options.changedFiles ?? []
13008
13303
  ]).map((item) => redactor.redact(item).value);
13009
- const recentFiles = uniqueStrings9([
13304
+ const recentFiles = uniqueStrings10([
13010
13305
  ...normalizeUnknownStringList(inputRepo.recentFiles),
13011
13306
  ...normalizeUnknownStringList(inputRepo.recent_files),
13012
13307
  ...normalizeUnknownStringList(input.recentFiles),
@@ -13074,18 +13369,18 @@ function normalizeEvidenceItem(value, section, index, redactor) {
13074
13369
  "command",
13075
13370
  "check"
13076
13371
  ]) ?? fallbackSummary(value, section, index);
13077
- const reasonCodes = uniqueStrings9([
13372
+ const reasonCodes = uniqueStrings10([
13078
13373
  `section_${section}`,
13079
13374
  ...normalizeUnknownStringList(value.reasonCodes),
13080
13375
  ...normalizeUnknownStringList(value.reason_codes)
13081
13376
  ]).map((item) => redactor.redact(item).value);
13082
- const files = uniqueStrings9([
13377
+ const files = uniqueStrings10([
13083
13378
  ...normalizeUnknownStringList(value.files),
13084
13379
  ...normalizeUnknownStringList(value.changedFiles),
13085
13380
  ...normalizeUnknownStringList(value.changed_files),
13086
13381
  ...normalizeUnknownStringList(value.file)
13087
13382
  ]).slice(0, MAX_LIST_ITEMS).map((item) => redactor.redact(item).value);
13088
- const commands = uniqueStrings9([
13383
+ const commands = uniqueStrings10([
13089
13384
  ...normalizeUnknownStringList(value.commands),
13090
13385
  ...normalizeUnknownStringList(value.command),
13091
13386
  ...normalizeUnknownStringList(value.checks),
@@ -13109,7 +13404,7 @@ function normalizeEvidenceItem(value, section, index, redactor) {
13109
13404
  });
13110
13405
  }
13111
13406
  function collectReasonCodes(input, cliValues, items, redactor) {
13112
- return uniqueStrings9([
13407
+ return uniqueStrings10([
13113
13408
  "coding_intelligence_ledger_v0",
13114
13409
  ...normalizeUnknownStringList(input.reasonCodes),
13115
13410
  ...normalizeUnknownStringList(input.reason_codes),
@@ -13140,13 +13435,13 @@ function buildCalibrationMetadata(input, cliValues, redactor) {
13140
13435
  "calibration",
13141
13436
  "calibration_metadata"
13142
13437
  );
13143
- const notes = uniqueStrings9([
13438
+ const notes = uniqueStrings10([
13144
13439
  ...normalizeUnknownStringList(calibration.notes),
13145
13440
  ...normalizeUnknownStringList(calibration.note),
13146
13441
  ...normalizeUnknownStringList(input.calibrationNotes),
13147
13442
  ...cliValues ?? []
13148
13443
  ]).slice(0, MAX_LIST_ITEMS).map((item) => redactor.redact(item).value);
13149
- const caveats = uniqueStrings9([
13444
+ const caveats = uniqueStrings10([
13150
13445
  ...normalizeUnknownStringList(calibration.caveats),
13151
13446
  ...normalizeUnknownStringList(calibration.caveat),
13152
13447
  ...normalizeUnknownStringList(input.calibrationCaveats)
@@ -13343,7 +13638,7 @@ function validIso(value) {
13343
13638
  const date = new Date(value);
13344
13639
  return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
13345
13640
  }
13346
- function uniqueStrings9(values) {
13641
+ function uniqueStrings10(values) {
13347
13642
  return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));
13348
13643
  }
13349
13644
  function compactWhitespace(value) {
@@ -15254,16 +15549,16 @@ function upsertLocalAdaptiveRoutingPolicy(workerRole) {
15254
15549
  mode: stringValue5(existing.mode) ?? "catalog",
15255
15550
  plannerRetainsReasoning: existing.plannerRetainsReasoning !== false,
15256
15551
  preferLocalWorkers: true,
15257
- allowedEndpointTypes: uniqueStrings10([
15552
+ allowedEndpointTypes: uniqueStrings11([
15258
15553
  ...normalizeStringList2(existing.allowedEndpointTypes),
15259
15554
  "local",
15260
15555
  "cloud"
15261
15556
  ]),
15262
- preferredEndpointTypes: uniqueStrings10([
15557
+ preferredEndpointTypes: uniqueStrings11([
15263
15558
  "local",
15264
15559
  ...normalizeStringList2(existing.preferredEndpointTypes)
15265
15560
  ]),
15266
- allowedWorkerClasses: uniqueStrings10([
15561
+ allowedWorkerClasses: uniqueStrings11([
15267
15562
  ...normalizeStringList2(existing.allowedWorkerClasses),
15268
15563
  workerRole,
15269
15564
  "documentation",
@@ -15323,9 +15618,9 @@ function defaultCapabilitiesForWorker(workerRole) {
15323
15618
  }
15324
15619
  function normalizeStringList2(value) {
15325
15620
  const values = Array.isArray(value) ? value : value === void 0 ? [] : [value];
15326
- return uniqueStrings10(values.map(stringValue5).filter((item) => Boolean(item)));
15621
+ return uniqueStrings11(values.map(stringValue5).filter((item) => Boolean(item)));
15327
15622
  }
15328
- function uniqueStrings10(values) {
15623
+ function uniqueStrings11(values) {
15329
15624
  return Array.from(new Set(values));
15330
15625
  }
15331
15626
  function stringValue5(value) {
@@ -15483,7 +15778,7 @@ function numberValue3(value) {
15483
15778
  const parsed = Number(text);
15484
15779
  return Number.isFinite(parsed) ? parsed : void 0;
15485
15780
  }
15486
- function uniqueStrings11(values) {
15781
+ function uniqueStrings12(values) {
15487
15782
  return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
15488
15783
  }
15489
15784
  function normalizeEnum(value) {
@@ -16566,7 +16861,7 @@ function buildProducerLoopArtifact(options) {
16566
16861
  workflowId ?? "unmanaged",
16567
16862
  options.phase?.id ? `phase:${options.phase.id}` : "final"
16568
16863
  ].join(":");
16569
- const reasonCodes = uniqueStrings11([
16864
+ const reasonCodes = uniqueStrings12([
16570
16865
  "producer_loop_v0",
16571
16866
  options.kind,
16572
16867
  `outcome_${options.outcome}`,
@@ -16758,8 +17053,8 @@ function summarizeProducerLoopArtifactFile(filePath, cwd) {
16758
17053
  relativePath: relativePath2,
16759
17054
  artifactHash: `sha256:${hashContent(content)}`,
16760
17055
  ledgerHash: stringValue6(parsed.ledgerHash),
16761
- reasonCodes: uniqueStrings11(stringArrayValue(ledger?.reasonCodes)),
16762
- files: uniqueStrings11(stringArrayValue(producer.files)),
17056
+ reasonCodes: uniqueStrings12(stringArrayValue(ledger?.reasonCodes)),
17057
+ files: uniqueStrings12(stringArrayValue(producer.files)),
16763
17058
  calibrationStatus: stringValue6(calibration?.status),
16764
17059
  reviewStatus,
16765
17060
  reviewOutcome: stringValue6(review?.outcome),
@@ -16806,10 +17101,10 @@ function buildProducerLoopReport(options = {}) {
16806
17101
  (left, right) => left.generatedAt === right.generatedAt ? left.relativePath.localeCompare(right.relativePath) : left.generatedAt.localeCompare(right.generatedAt)
16807
17102
  );
16808
17103
  const invalidArtifacts = summaries.map((entry) => entry.invalid).filter((entry) => Boolean(entry));
16809
- const producerKinds = uniqueStrings11(artifacts.map((artifact) => artifact.producerKind)).filter(
17104
+ const producerKinds = uniqueStrings12(artifacts.map((artifact) => artifact.producerKind)).filter(
16810
17105
  (kind) => isProducerLoopProducerKind(kind)
16811
17106
  );
16812
- const workflowIds = uniqueStrings11(
17107
+ const workflowIds = uniqueStrings12(
16813
17108
  artifacts.map((artifact) => artifact.workflowId).filter((id) => Boolean(id))
16814
17109
  );
16815
17110
  const reasonCodeCounts = countStringOccurrences(
@@ -16960,7 +17255,7 @@ function readProducerLoopArtifactForReview(filePath) {
16960
17255
  return parsed;
16961
17256
  }
16962
17257
  function applyProducerLoopArtifactReview(artifact, options) {
16963
- const reviewNotes = uniqueStrings11(options.notes ?? []);
17258
+ const reviewNotes = uniqueStrings12(options.notes ?? []);
16964
17259
  const reviewNote = options.status === "sample_rejected" ? "Sample rejected by local operator review." : "Sample reviewed by local operator.";
16965
17260
  const existingCalibration = artifact.calibration ?? {
16966
17261
  status: "sample_unreviewed",
@@ -16975,7 +17270,7 @@ function applyProducerLoopArtifactReview(artifact, options) {
16975
17270
  status: options.status,
16976
17271
  sampleSize: 1,
16977
17272
  hardGateReady: false,
16978
- notes: uniqueStrings11([
17273
+ notes: uniqueStrings12([
16979
17274
  ...stringArrayValue(existingCalibration.notes),
16980
17275
  reviewNote,
16981
17276
  ...reviewNotes.map((note) => `Review note: ${note}`)
@@ -17185,7 +17480,7 @@ function applyDecisionRequestLocally(options) {
17185
17480
  reject: isRejectChoice(options.choice),
17186
17481
  outcome: isRejectChoice(options.choice) ? "false_positive" : "useful",
17187
17482
  reviewer: options.reviewer,
17188
- notes: uniqueStrings11([
17483
+ notes: uniqueStrings12([
17189
17484
  `decision_request:${request.requestId}`,
17190
17485
  ...options.note ? [options.note] : []
17191
17486
  ])
@@ -17199,12 +17494,17 @@ function applyDecisionRequestLocally(options) {
17199
17494
  function recurringPolicySuggestionKey(record) {
17200
17495
  return stableDecisionJsonStringify({
17201
17496
  producer: record.request.producer.kind,
17202
- decision: record.request.decision,
17203
17497
  choice: record.response.choice,
17204
- note: compactWhitespace2(record.response.note ?? "").toLowerCase(),
17205
- reasonCodes: record.request.evidence.reasonCodes
17498
+ targetCategory: recurringPolicySuggestionTargetCategory(record.request)
17206
17499
  });
17207
17500
  }
17501
+ function recurringPolicySuggestionTargetCategory(request) {
17502
+ const evidenceCategories = request.evidence.items?.map((item) => {
17503
+ const metadata = isRecord8(item.metadata) ? item.metadata : void 0;
17504
+ return stringValue6(metadata?.category) ?? stringValue6(metadata?.type) ?? item.kind;
17505
+ }).filter((value) => Boolean(value)) ?? [];
17506
+ return uniqueStrings12(evidenceCategories).sort().join("|") || request.evidence.applyPath || request.decision;
17507
+ }
17208
17508
  function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
17209
17509
  const grouped = /* @__PURE__ */ new Map();
17210
17510
  for (const record of resolvedRecords) {
@@ -17221,6 +17521,7 @@ function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
17221
17521
  const refs = repeated.map((record) => record.request.requestId);
17222
17522
  const note = compactWhitespace2(latest.response.note ?? "");
17223
17523
  const policyTitle = note ? `Never ask twice for: ${note}` : `Never ask twice for ${latest.request.decision} -> ${latest.response.choice}`;
17524
+ const targetCategory = recurringPolicySuggestionTargetCategory(latest.request);
17224
17525
  return buildDecisionRequest({
17225
17526
  producer: {
17226
17527
  kind: "policy_suggestion",
@@ -17240,12 +17541,12 @@ function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
17240
17541
  status: record.response.choice,
17241
17542
  files: record.request.evidence.files?.slice(0, 12)
17242
17543
  })),
17243
- reasonCodes: uniqueStrings11([
17544
+ reasonCodes: uniqueStrings12([
17244
17545
  "recurring_decision_receipts",
17245
17546
  "never_ask_twice_candidate",
17246
17547
  ...latest.request.evidence.reasonCodes
17247
17548
  ]),
17248
- files: uniqueStrings11(repeated.flatMap((record) => record.request.evidence.files ?? [])),
17549
+ files: uniqueStrings12(repeated.flatMap((record) => record.request.evidence.files ?? [])),
17249
17550
  applyPath: "manual_context_review",
17250
17551
  applyCommand: "Review the suggested rule and add it to the appropriate project policy or AGENTS.md section manually."
17251
17552
  },
@@ -17255,10 +17556,9 @@ function buildRecurringDecisionPolicySuggestionRequest(resolvedRecords) {
17255
17556
  fingerprintParts: [
17256
17557
  "recurring_decision_policy_suggestion",
17257
17558
  repeated.length,
17258
- latest.request.decision,
17559
+ latest.request.producer.kind,
17259
17560
  latest.response.choice,
17260
- compactWhitespace2(latest.response.note ?? "").toLowerCase(),
17261
- latest.request.evidence.reasonCodes
17561
+ targetCategory
17262
17562
  ]
17263
17563
  });
17264
17564
  }
@@ -17388,12 +17688,12 @@ function buildProducerTriageRequest(report) {
17388
17688
  summary: `${candidates.length} unreviewed Producer Loop samples; ${report.invalidArtifacts.length} invalid artifacts; hardGateReady remains false.`,
17389
17689
  refs,
17390
17690
  items,
17391
- reasonCodes: uniqueStrings11([
17691
+ reasonCodes: uniqueStrings12([
17392
17692
  "triage_rules_v0",
17393
17693
  "producer_loop_report_valid",
17394
17694
  ...report.invalidArtifacts.length === 0 ? ["no_invalid_artifacts"] : ["invalid_artifacts_present"]
17395
17695
  ]),
17396
- files: uniqueStrings11(candidates.flatMap((artifact) => artifact.files)),
17696
+ files: uniqueStrings12(candidates.flatMap((artifact) => artifact.files)),
17397
17697
  applyPath: "workflow producer-review",
17398
17698
  applyCommand: "snipara-companion workflow decide <request-id> --choose accept_all --reviewer <name>"
17399
17699
  },
@@ -17454,7 +17754,7 @@ async function workflowDecisionProducerMemoryCommand(options) {
17454
17754
  const request2 = buildMemoryReviewDecisionRequest({
17455
17755
  ...reviewItem,
17456
17756
  action,
17457
- recommendation: options.reviewerHint ?? reviewItem.recommendation,
17757
+ recommendation: options.reviewerHint ?? action,
17458
17758
  options: action === "verify" ? ["verify", "keep_pending", "invalidate"] : action === "invalidate" ? ["invalidate", "keep", "inspect"] : reviewItem.options.includes(action) ? reviewItem.options : [action, ...reviewItem.options.filter((option) => option !== action)]
17459
17759
  });
17460
17760
  const write2 = writeDecisionRequest(request2);
@@ -19114,8 +19414,8 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
19114
19414
  runtimeCatalog: catalog,
19115
19415
  routingCard: {
19116
19416
  ...routing.routingCard,
19117
- reasons: uniqueStrings11(reasons),
19118
- warnings: uniqueStrings11([...routing.routingCard.warnings, ...gatewayWarnings])
19417
+ reasons: uniqueStrings12(reasons),
19418
+ warnings: uniqueStrings12([...routing.routingCard.warnings, ...gatewayWarnings])
19119
19419
  }
19120
19420
  };
19121
19421
  } catch (error) {
@@ -19132,7 +19432,7 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
19132
19432
  },
19133
19433
  routingCard: {
19134
19434
  ...routing.routingCard,
19135
- warnings: uniqueStrings11([...routing.routingCard.warnings, warning])
19435
+ warnings: uniqueStrings12([...routing.routingCard.warnings, warning])
19136
19436
  }
19137
19437
  };
19138
19438
  }
@@ -19298,7 +19598,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
19298
19598
  const approvalRequired = booleanValue3(
19299
19599
  isRecord8(resolution?.policyDecision) ? resolution.policyDecision.approvalRequired : void 0
19300
19600
  );
19301
- const gatewayWarnings = uniqueStrings11([
19601
+ const gatewayWarnings = uniqueStrings12([
19302
19602
  ...resolutionWarnings,
19303
19603
  ...selectedCandidate ? [] : ["Local orchestrator did not select a worker candidate and will fail closed."]
19304
19604
  ]);
@@ -19319,7 +19619,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
19319
19619
  ...routing.routingCard,
19320
19620
  ...selectedCandidate?.workerClass ? { recommendedWorkerClass: selectedCandidate.workerClass } : {},
19321
19621
  ...approvalRequired !== void 0 ? { humanApprovalRequired: approvalRequired } : {},
19322
- reasons: uniqueStrings11([
19622
+ reasons: uniqueStrings12([
19323
19623
  ...routing.routingCard.reasons,
19324
19624
  ...resolutionReasons,
19325
19625
  ...selectedCandidate ? [
@@ -19328,7 +19628,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
19328
19628
  ...stringValue6(selectedEndpoint?.model) ? [`selected local model ${String(selectedEndpoint?.model)}`] : []
19329
19629
  ] : ["local orchestrator could not resolve a concrete worker candidate"]
19330
19630
  ]),
19331
- warnings: uniqueStrings11([...routing.routingCard.warnings, ...gatewayWarnings])
19631
+ warnings: uniqueStrings12([...routing.routingCard.warnings, ...gatewayWarnings])
19332
19632
  }
19333
19633
  };
19334
19634
  } catch (error) {
@@ -19345,7 +19645,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
19345
19645
  },
19346
19646
  routingCard: {
19347
19647
  ...routing.routingCard,
19348
- warnings: uniqueStrings11([...routing.routingCard.warnings, warning])
19648
+ warnings: uniqueStrings12([...routing.routingCard.warnings, warning])
19349
19649
  }
19350
19650
  };
19351
19651
  }
@@ -20215,7 +20515,7 @@ function scanOnboardFolder(options) {
20215
20515
  files.sort((a, b) => a.path.localeCompare(b.path));
20216
20516
  return files;
20217
20517
  }
20218
- function addSignal(signals, signal) {
20518
+ function addSignal2(signals, signal) {
20219
20519
  if (!signals.includes(signal)) {
20220
20520
  signals.push(signal);
20221
20521
  }
@@ -20229,7 +20529,7 @@ function classifyOnboardFolder(rootDir, files, overrideMode = "auto") {
20229
20529
  let supportedPlainDocuments = 0;
20230
20530
  if (fs21.existsSync(path20.join(rootDir, ".git"))) {
20231
20531
  codeScore += 4;
20232
- addSignal(codeSignals, ".git directory");
20532
+ addSignal2(codeSignals, ".git directory");
20233
20533
  }
20234
20534
  for (const file of files) {
20235
20535
  const base = path20.basename(file.path);
@@ -20237,22 +20537,22 @@ function classifyOnboardFolder(rootDir, files, overrideMode = "auto") {
20237
20537
  const firstSegment = firstPathSegment(file.path);
20238
20538
  if (CODE_MARKER_FILES.has(base)) {
20239
20539
  codeScore += 3;
20240
- addSignal(codeSignals, base);
20540
+ addSignal2(codeSignals, base);
20241
20541
  }
20242
20542
  if (CODE_MARKER_DIRS.has(firstSegment)) {
20243
20543
  codeScore += 1.5;
20244
- addSignal(codeSignals, `${firstSegment}/ directory`);
20544
+ addSignal2(codeSignals, `${firstSegment}/ directory`);
20245
20545
  }
20246
20546
  if (CODE_SOURCE_EXTENSIONS.has(ext)) {
20247
20547
  codeSourceFiles += 1;
20248
20548
  }
20249
20549
  if ([".docx", ".pdf", ".pptx", ".vsdx", ".xlsx", ".xls", ".csv", ".tsv"].includes(ext)) {
20250
20550
  businessScore += 2;
20251
- addSignal(businessSignals, `${ext} documents`);
20551
+ addSignal2(businessSignals, `${ext} documents`);
20252
20552
  }
20253
20553
  if (hasBusinessKeyword(file.path)) {
20254
20554
  businessScore += 2;
20255
- addSignal(businessSignals, `business keyword in ${file.path}`);
20555
+ addSignal2(businessSignals, `business keyword in ${file.path}`);
20256
20556
  }
20257
20557
  if (file.supported && [".md", ".markdown", ".mdx", ".txt", ".rst", ".adoc"].includes(ext)) {
20258
20558
  supportedPlainDocuments += 1;
@@ -20261,10 +20561,10 @@ function classifyOnboardFolder(rootDir, files, overrideMode = "auto") {
20261
20561
  codeScore += Math.min(5, codeSourceFiles * 0.25);
20262
20562
  businessScore += Math.min(3, supportedPlainDocuments * 0.35);
20263
20563
  if (codeSourceFiles > 0) {
20264
- addSignal(codeSignals, `${codeSourceFiles} source-looking files`);
20564
+ addSignal2(codeSignals, `${codeSourceFiles} source-looking files`);
20265
20565
  }
20266
20566
  if (supportedPlainDocuments > 0) {
20267
- addSignal(businessSignals, `${supportedPlainDocuments} supported text documents`);
20567
+ addSignal2(businessSignals, `${supportedPlainDocuments} supported text documents`);
20268
20568
  }
20269
20569
  const codeStrong = codeScore >= 3;
20270
20570
  const businessStrong = businessScore >= 2.5 || !codeStrong && files.some((file) => file.supported);
@@ -21148,8 +21448,8 @@ function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = [
21148
21448
  ...routing,
21149
21449
  routingCard: {
21150
21450
  ...routing.routingCard,
21151
- reasons: uniqueStrings11([...routing.routingCard.reasons, ...policyReasons]),
21152
- warnings: uniqueStrings11([...routing.routingCard.warnings, ...policyWarnings])
21451
+ reasons: uniqueStrings12([...routing.routingCard.reasons, ...policyReasons]),
21452
+ warnings: uniqueStrings12([...routing.routingCard.warnings, ...policyWarnings])
21153
21453
  }
21154
21454
  };
21155
21455
  }
@@ -21165,7 +21465,7 @@ function normalizeAdaptiveRoutingMode(value) {
21165
21465
  return normalized === "off" || normalized === "recommend" || normalized === "catalog" ? normalized : null;
21166
21466
  }
21167
21467
  function normalizeAdaptiveWorkerClasses(values) {
21168
- return uniqueStrings11((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
21468
+ return uniqueStrings12((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
21169
21469
  (value) => ["documentation", "tests", "review", "coding"].includes(value)
21170
21470
  );
21171
21471
  }
@@ -21601,12 +21901,80 @@ function printAgenticTimeline(timeline) {
21601
21901
  }
21602
21902
  console.log("");
21603
21903
  }
21904
+ function escapeRegExp5(value) {
21905
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21906
+ }
21907
+ function localWorkspacePathAliases() {
21908
+ const cwd = process.cwd();
21909
+ const aliases = /* @__PURE__ */ new Set([cwd]);
21910
+ try {
21911
+ aliases.add(fs21.realpathSync(cwd));
21912
+ } catch {
21913
+ }
21914
+ for (const alias of [...aliases]) {
21915
+ if (alias.startsWith("/private/var/")) aliases.add(alias.replace(/^\/private/, ""));
21916
+ else if (alias.startsWith("/var/")) aliases.add(`/private${alias}`);
21917
+ }
21918
+ return [...aliases].filter(Boolean).sort((left, right) => right.length - left.length);
21919
+ }
21920
+ function redactTimelineText(value, maxLength = 180) {
21921
+ let text = toPreview(value, maxLength);
21922
+ for (const alias of localWorkspacePathAliases()) {
21923
+ text = text.replace(new RegExp(escapeRegExp5(alias), "g"), "[workspace]");
21924
+ }
21925
+ return text.replace(/\/Users\/[^/\s)]+/g, "[home]").replace(/\.snipara\/[^\s,)]+/g, "[local-state]").replace(/\b(api[_-]?key|token|secret|password)=\S+/gi, "$1=[redacted]");
21926
+ }
21927
+ function redactTimelineFile(file) {
21928
+ const workspaceAlias = localWorkspacePathAliases().find(
21929
+ (alias) => path20.isAbsolute(file) && file.startsWith(alias)
21930
+ );
21931
+ const relative8 = workspaceAlias ? path20.relative(workspaceAlias, file) : file;
21932
+ if (relative8.startsWith(".snipara/") || relative8 === ".snipara") {
21933
+ return "[local-state]";
21934
+ }
21935
+ return redactTimelineText(relative8, 120);
21936
+ }
21937
+ function formatAgenticTimelineMarkdown(timeline) {
21938
+ const lines = [
21939
+ "# Workflow Timeline",
21940
+ "",
21941
+ `Generated: ${timeline.generatedAt}`,
21942
+ `Events: ${timeline.events.length}`,
21943
+ "",
21944
+ "> Redacted local export. Absolute paths, local state paths, and secret-like fragments are removed.",
21945
+ ""
21946
+ ];
21947
+ for (const event of timeline.events) {
21948
+ lines.push(`## ${redactTimelineText(event.time, 40)} - ${redactTimelineText(event.kind, 80)}`);
21949
+ lines.push("");
21950
+ lines.push(`- Source: ${redactTimelineText(event.source, 80)}`);
21951
+ lines.push(`- Title: ${redactTimelineText(event.title, 180)}`);
21952
+ if (event.detail) {
21953
+ lines.push(`- Summary: ${redactTimelineText(event.detail, 220)}`);
21954
+ }
21955
+ if (event.files?.length) {
21956
+ lines.push(
21957
+ `- Files: ${event.files.slice(0, 6).map(redactTimelineFile).join(", ")}${event.files.length > 6 ? ` (+${event.files.length - 6})` : ""}`
21958
+ );
21959
+ }
21960
+ lines.push("");
21961
+ }
21962
+ return `${lines.join("\n").trimEnd()}
21963
+ `;
21964
+ }
21604
21965
  async function workflowTimelineCommand(options) {
21605
21966
  const timeline = buildAgenticTimeline({ limit: options.limit });
21606
21967
  if (options.json) {
21607
21968
  printJson2(timeline);
21608
21969
  return;
21609
21970
  }
21971
+ if (options.exportFormat) {
21972
+ if (options.exportFormat !== "md") {
21973
+ throw new Error("Unsupported workflow timeline export format. Use --export md.");
21974
+ }
21975
+ process.stdout.write(formatAgenticTimelineMarkdown(timeline));
21976
+ return;
21977
+ }
21610
21978
  printAgenticTimeline(timeline);
21611
21979
  }
21612
21980
  async function workflowSessionCommand(options) {
@@ -21638,6 +22006,17 @@ async function workflowSessionCommand(options) {
21638
22006
  printKeyValue2("Resolved decisions:", snapshot.decisions.resolvedCount);
21639
22007
  printKeyValue2("Producer artifacts:", snapshot.producerLoop.artifactCount);
21640
22008
  printKeyValue2("Team Sync active work:", snapshot.teamSync.activeWorkCount);
22009
+ printKeyValue2(
22010
+ "Intent:",
22011
+ `${snapshot.intentDetection.intent} (${snapshot.intentDetection.confidence})`
22012
+ );
22013
+ printKeyValue2(
22014
+ "Suggested mode:",
22015
+ `${snapshot.intentDetection.advisoryRouting.suggestedWorkflowMode} (advisory)`
22016
+ );
22017
+ if (snapshot.intentDetection.signals.length > 0) {
22018
+ console.log(`Intent signals: ${snapshot.intentDetection.signals.slice(0, 5).join(", ")}`);
22019
+ }
21641
22020
  printKeyValue2("Hard routing allowed:", String(snapshot.routing.hardRoutingAllowed));
21642
22021
  console.log(`Reason: ${snapshot.routing.reason}`);
21643
22022
  printKeyValue2("Snapshot:", snapshot.source.snapshotPath);
@@ -21864,10 +22243,12 @@ async function workflowResumeCommand(options) {
21864
22243
  });
21865
22244
  const teamSyncResume = await loadWorkflowTeamSyncResume(state);
21866
22245
  const runtimeResume = await loadWorkflowRuntimeResumePlan(state);
22246
+ const localSessionSnapshot = writeSessionSnapshot({ limit: 8 });
21867
22247
  if (options.json) {
21868
22248
  printJson2({
21869
22249
  workflow: state,
21870
22250
  pending_decision_count: safeDecisionPendingCount(),
22251
+ local_session_snapshot: localSessionSnapshot,
21871
22252
  session_bootstrap: bootstrap,
21872
22253
  session_bootstrap_quality: bootstrapQuality,
21873
22254
  team_sync_resume: teamSyncResume?.data ?? null,
@@ -21887,6 +22268,7 @@ async function workflowResumeCommand(options) {
21887
22268
  includeSessionContext: resolvedContextTokens > 0
21888
22269
  });
21889
22270
  printSessionBootstrapQuality(bootstrapQuality);
22271
+ printWorkflowLocalSessionSnapshot(localSessionSnapshot);
21890
22272
  printWorkflowTeamSyncResume(teamSyncResume);
21891
22273
  printWorkflowRuntimeResumePlan(runtimeResume);
21892
22274
  printManagedWorkflowResumeBoundary();
@@ -21914,6 +22296,37 @@ async function loadWorkflowTeamSyncResume(state) {
21914
22296
  };
21915
22297
  }
21916
22298
  }
22299
+ function printWorkflowLocalSessionSnapshot(snapshot) {
22300
+ console.log("");
22301
+ console.log(import_chalk6.default.bold("Local Session Snapshot"));
22302
+ if (snapshot.summary.latestActivityAt) {
22303
+ printKeyValue2("Latest activity:", snapshot.summary.latestActivityAt);
22304
+ }
22305
+ if (snapshot.summary.latestActivityTitle) {
22306
+ printKeyValue2("Latest title:", toPreview(snapshot.summary.latestActivityTitle, 160));
22307
+ }
22308
+ printKeyValue2("Risk:", snapshot.summary.risk);
22309
+ if (snapshot.summary.riskReasons.length > 0) {
22310
+ console.log(`Risk reasons: ${snapshot.summary.riskReasons.slice(0, 3).join("; ")}`);
22311
+ }
22312
+ if (snapshot.summary.touchedFiles.length > 0) {
22313
+ console.log(`Touched files: ${snapshot.summary.touchedFiles.slice(0, 8).join(", ")}`);
22314
+ }
22315
+ printKeyValue2(
22316
+ "Intent:",
22317
+ `${snapshot.intentDetection.intent} (${snapshot.intentDetection.confidence})`
22318
+ );
22319
+ printKeyValue2(
22320
+ "Suggested mode:",
22321
+ `${snapshot.intentDetection.advisoryRouting.suggestedWorkflowMode} (advisory)`
22322
+ );
22323
+ if (snapshot.intentDetection.signals.length > 0) {
22324
+ console.log(`Intent signals: ${snapshot.intentDetection.signals.slice(0, 5).join(", ")}`);
22325
+ }
22326
+ printKeyValue2("Hard routing allowed:", String(snapshot.intentDetection.hardRoutingAllowed));
22327
+ printKeyValue2("Next action:", snapshot.summary.recommendedNextAction);
22328
+ printKeyValue2("Snapshot:", snapshot.source.snapshotPath);
22329
+ }
21917
22330
  function printWorkflowTeamSyncResume(result) {
21918
22331
  if (!result) {
21919
22332
  return;
@@ -25262,7 +25675,7 @@ function normalizeCaptureEvent(event, now, index) {
25262
25675
  const reasons = normalizeStringList3(event.reason).map(redactText);
25263
25676
  const evidence = normalizeStringList3(event.evidence).map(redactText);
25264
25677
  const commands = normalizeStringList3(event.commands).map(redactText);
25265
- const redactionPatterns = uniqueStrings12([
25678
+ const redactionPatterns = uniqueStrings13([
25266
25679
  ...summary.patterns,
25267
25680
  ...outcome.patterns,
25268
25681
  ...status.patterns,
@@ -25295,7 +25708,7 @@ function normalizeEventKind(value) {
25295
25708
  return "feedback";
25296
25709
  }
25297
25710
  function reasonCodesForEvent(event, kind, status) {
25298
- return uniqueStrings12([
25711
+ return uniqueStrings13([
25299
25712
  "why_outcome_capture_v1",
25300
25713
  "review_pending_authority",
25301
25714
  `source_${event.kind}`,
@@ -25360,7 +25773,7 @@ function readEventsFromFile(filePath) {
25360
25773
  function stableHash2(parts) {
25361
25774
  return (0, import_node_crypto7.createHash)("sha256").update(parts.join("\0")).digest("hex");
25362
25775
  }
25363
- function uniqueStrings12(values) {
25776
+ function uniqueStrings13(values) {
25364
25777
  return Array.from(new Set(values.filter(Boolean)));
25365
25778
  }
25366
25779
  function compactWhitespace3(value) {
@@ -26652,6 +27065,14 @@ async function buildProjectIntelligenceBrief(options) {
26652
27065
  diffSummary: options.diffSummary
26653
27066
  })
26654
27067
  };
27068
+ try {
27069
+ brief.localSessionSnapshot = readSessionSnapshot() ?? buildSessionSnapshot({ limit: 8 });
27070
+ } catch (error) {
27071
+ errors.push({
27072
+ surface: "local_session_snapshot",
27073
+ message: error instanceof Error ? error.message : String(error)
27074
+ });
27075
+ }
26655
27076
  try {
26656
27077
  brief.resumeContext = await client.callTool("snipara_resume_context", {
26657
27078
  ...branch ? { branch } : {},
@@ -26750,6 +27171,36 @@ function printResumeContext(result) {
26750
27171
  }
26751
27172
  }
26752
27173
  }
27174
+ function printLocalSessionSnapshot(snapshot) {
27175
+ if (!snapshot) {
27176
+ console.log(import_chalk9.default.yellow("Unavailable"));
27177
+ return;
27178
+ }
27179
+ if (snapshot.summary.latestActivityAt) {
27180
+ console.log(`Latest activity: ${snapshot.summary.latestActivityAt}`);
27181
+ }
27182
+ if (snapshot.summary.latestActivityTitle) {
27183
+ console.log(`Latest title: ${preview3(snapshot.summary.latestActivityTitle, 220)}`);
27184
+ }
27185
+ console.log(`Risk: ${snapshot.summary.risk}`);
27186
+ if (snapshot.summary.riskReasons.length > 0) {
27187
+ console.log(`Risk reasons: ${snapshot.summary.riskReasons.slice(0, 3).join("; ")}`);
27188
+ }
27189
+ if (snapshot.summary.touchedFiles.length > 0) {
27190
+ console.log(`Touched files: ${snapshot.summary.touchedFiles.slice(0, 8).join(", ")}`);
27191
+ }
27192
+ console.log(
27193
+ `Intent: ${snapshot.intentDetection.intent} (${snapshot.intentDetection.confidence})`
27194
+ );
27195
+ console.log(
27196
+ `Suggested mode: ${snapshot.intentDetection.advisoryRouting.suggestedWorkflowMode} (advisory)`
27197
+ );
27198
+ if (snapshot.intentDetection.signals.length > 0) {
27199
+ console.log(`Intent signals: ${snapshot.intentDetection.signals.slice(0, 5).join(", ")}`);
27200
+ }
27201
+ console.log(`Hard routing allowed: ${snapshot.intentDetection.hardRoutingAllowed}`);
27202
+ console.log(`Next action: ${snapshot.summary.recommendedNextAction}`);
27203
+ }
26753
27204
  function printMemoryHealth2(result) {
26754
27205
  if (!result) {
26755
27206
  console.log(import_chalk9.default.yellow("Skipped or unavailable"));
@@ -26824,6 +27275,9 @@ async function projectIntelligenceBriefCommand(options) {
26824
27275
  console.log(import_chalk9.default.bold("Continuity"));
26825
27276
  printResumeContext(brief.resumeContext);
26826
27277
  console.log("");
27278
+ console.log(import_chalk9.default.bold("Local Session"));
27279
+ printLocalSessionSnapshot(brief.localSessionSnapshot);
27280
+ console.log("");
26827
27281
  console.log(import_chalk9.default.bold("Memory Authority And Health"));
26828
27282
  printMemoryHealth2(brief.memoryHealth);
26829
27283
  console.log("");
@@ -29531,7 +29985,7 @@ function mergeManagedCollaborationHookBlock(hookName, currentContent, block) {
29531
29985
  const startMarker = collaborationHookBlockMarker(hookName, "start");
29532
29986
  const endMarker = collaborationHookBlockMarker(hookName, "end");
29533
29987
  const managedBlockPattern = new RegExp(
29534
- `${escapeRegExp5(startMarker)}[\\s\\S]*?${escapeRegExp5(endMarker)}\\n?`,
29988
+ `${escapeRegExp6(startMarker)}[\\s\\S]*?${escapeRegExp6(endMarker)}\\n?`,
29535
29989
  "m"
29536
29990
  );
29537
29991
  const base = currentContent && currentContent.trim() ? currentContent : "#!/usr/bin/env sh\n";
@@ -30077,7 +30531,7 @@ function safeUsername() {
30077
30531
  function formatError2(error) {
30078
30532
  return error instanceof Error ? error.message : String(error);
30079
30533
  }
30080
- function escapeRegExp5(value) {
30534
+ function escapeRegExp6(value) {
30081
30535
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
30082
30536
  }
30083
30537
  function ensureTrailingNewline3(value) {
@@ -31151,9 +31605,10 @@ workflow.command("start").description(
31151
31605
  workflow.command("status").description("Show the current local Snipara workflow state").option("--json", "Print raw JSON").action(async (options) => {
31152
31606
  await workflowStatusCommand({ json: options.json });
31153
31607
  });
31154
- workflow.command("timeline").description("Show the append-only local activity timeline for this workflow session").option("-l, --limit <number>", "Maximum number of events", "20").option("--json", "Print raw JSON").action(async (options) => {
31608
+ workflow.command("timeline").description("Show the append-only local activity timeline for this workflow session").option("-l, --limit <number>", "Maximum number of events", "20").option("--export <format>", "Export redacted timeline artifact (md)").option("--json", "Print raw JSON").action(async (options) => {
31155
31609
  await workflowTimelineCommand({
31156
31610
  limit: parseInt(options.limit, 10),
31611
+ exportFormat: options.export,
31157
31612
  json: Boolean(options.json)
31158
31613
  });
31159
31614
  });
@@ -32353,6 +32808,7 @@ if (require.main === module) {
32353
32808
  readLocalCodePromotionState,
32354
32809
  readLocalSourceSnapshot,
32355
32810
  readLocalWorkersConfig,
32811
+ readSessionSnapshot,
32356
32812
  realityCheckCommand,
32357
32813
  referencesIngestCommand,
32358
32814
  referencesScanCommand,
@@ -177,6 +177,7 @@ snipara-companion brief --task "ship auth hardening" --changed-files src/auth.ts
177
177
  snipara-companion reality-check --task "ship auth hardening" --changed-files src/auth.ts --enforce
178
178
  snipara-companion timeline
179
179
  snipara-companion workflow timeline
180
+ snipara-companion workflow timeline --export md
180
181
  snipara-companion workflow session --json
181
182
  snipara-companion workflow phase-commit build --summary "tests green"
182
183
  snipara-companion workflow impact-gate
@@ -218,11 +219,15 @@ snipara-companion workflow resume --include-session-context
218
219
  commits, final commits, and Team Sync handoffs.
219
220
  - `workflow timeline` reads the append-only local activity log at
220
221
  `.snipara/activity/timeline.jsonl`, enriched by workflow, Producer Loop,
221
- Decision Request, Team Sync, and journal-adjacent events.
222
+ Decision Request, Team Sync, and journal-adjacent events. Add `--export md`
223
+ for a compact redacted Markdown artifact suitable for handoff or publication.
222
224
  - `workflow session` writes `.snipara/activity/session.json`, a fast Session
223
- Snapshot V0 for local resume and Orchestrator dogfood. It is observational
224
- only and reports `hardRoutingAllowed=false` until explicit policy and receipts
225
- allow harder routing.
225
+ Snapshot V0 for local resume and Orchestrator dogfood. It includes latest
226
+ activity, risk reasons, touched files, next action, and advisory Intent
227
+ Detection V0. Intent Detection V0 reports intent, confidence, evidence
228
+ counts, reason-code signals, and an advisory suggested workflow mode. It is
229
+ observational only and reports `hardRoutingAllowed=false` until explicit
230
+ policy and receipts allow harder routing.
226
231
  - `workflow impact-gate` audits committed local workflow phases that are ahead
227
232
  of upstream but not pushed. It does not push, and dirty working-tree files are
228
233
  reported separately from the committed diff.
@@ -244,6 +249,8 @@ snipara-companion workflow resume --include-session-context
244
249
  - `memory reviews --scope project --emit-decisions` reads hosted memory review
245
250
  queue, clean-candidate, and duplicate-candidate surfaces, summarizes each item,
246
251
  and writes local Decision Request artifacts without mutating hosted memory.
252
+ JSON output includes `emittedCount`, `emittedRequestIds`, and an `emitted`
253
+ summary for direct agent follow-up.
247
254
  - `workflow producer-report` scans local Producer Loop artifacts emitted by
248
255
  workflow phase/final commits or exported PR Answer Pack decision-capture
249
256
  producers, then reports adoption, producer kinds, workflow ids, reason-code
@@ -1101,10 +1108,10 @@ Semantics:
1101
1108
  - `snipara-companion brief` = short alias for `snipara-companion intelligence brief`
1102
1109
  - `snipara-companion reality-check` = local Project Reality Check plus Intent Ledger and Unknown Registry coverage for supplied or Git-derived changed files; `--enforce` is an opt-in strict mode for calibrated hooks
1103
1110
  - `snipara-companion timeline` = local timeline of workflow starts, phase starts, phase commits, final commits, and Team Sync handoffs
1104
- - `snipara-companion workflow timeline` = append-only activity timeline from `.snipara/activity/timeline.jsonl`, including workflow, Producer Loop, Decision Request, and Team Sync events emitted by Companion commands
1105
- - `snipara-companion workflow session` = writes and prints Session Snapshot V0 at `.snipara/activity/session.json` with workflow/session counts, Producer Loop calibration, decision counts, Team Sync counts, and `hardRoutingAllowed=false`
1111
+ - `snipara-companion workflow timeline` = append-only activity timeline from `.snipara/activity/timeline.jsonl`, including workflow, Producer Loop, Decision Request, and Team Sync events emitted by Companion commands; add `--export md` for a redacted Markdown artifact
1112
+ - `snipara-companion workflow session` = writes and prints Session Snapshot V0 at `.snipara/activity/session.json` with latest activity, risk, touched files, next action, advisory Intent Detection V0 intent/confidence/signals/suggested mode, workflow/session counts, Producer Loop calibration, decision counts, Team Sync counts, and `hardRoutingAllowed=false`
1106
1113
  - `snipara-companion handoff` = top-level agent-ready Markdown/JSON handoff artifact plus the same local/hosted Team Sync handoff persistence
1107
- - `snipara-companion intelligence brief` = one local Project Intelligence brief that combines resume context, memory health, and code impact for a task
1114
+ - `snipara-companion intelligence brief` = one local Project Intelligence brief that combines local Session Snapshot, hosted resume context, memory health, and code impact for a task
1108
1115
  - `snipara-companion intelligence reality-check` = Project Intelligence namespace alias for the same local Reality Check gate
1109
1116
  - `snipara-companion intelligence ledger-export` = structured redacted Coding Intelligence Ledger JSON for replay, review, and commercial proof assets without dumping raw transcripts
1110
1117
  - `snipara-companion run` = production Project Intelligence flow that combines the brief, guard action cards, package review, verification hints, and a final weighted Judgment Card
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.2.2",
3
+ "version": "3.2.3",
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": {