snipara-companion 1.3.1 → 1.3.2
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 +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +155 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,14 @@ The mental model is intentionally close to Git:
|
|
|
112
112
|
| `git format-patch` | `snipara-companion handoff` |
|
|
113
113
|
| `git checkout` | `snipara-companion workflow resume` |
|
|
114
114
|
|
|
115
|
+
`snipara-companion final-commit` closes the local workflow and asks the hosted
|
|
116
|
+
API only for the final Team Sync handoff. The CLI sends a compact summary with a
|
|
117
|
+
longer timeout, retries once with a shorter summary on transient hosted failures,
|
|
118
|
+
and then records a local fallback handoff in `.snipara/team-sync/session.json`
|
|
119
|
+
if the hosted call still times out. A hosted final-commit timeout does not modify
|
|
120
|
+
Git state. Custom final-commit categories are namespaced under `final-commit`
|
|
121
|
+
before the hosted call so they stay on the handoff-only path.
|
|
122
|
+
|
|
115
123
|
## Verification Plans
|
|
116
124
|
|
|
117
125
|
Use `verify` when an agent asks what to prove before handoff or release:
|
package/dist/index.d.ts
CHANGED
|
@@ -949,6 +949,8 @@ declare class RLMClient {
|
|
|
949
949
|
category?: string;
|
|
950
950
|
outcome?: "completed" | "partial" | "blocked" | "abandoned";
|
|
951
951
|
filesTouched?: string[];
|
|
952
|
+
persistTypes?: string[];
|
|
953
|
+
handoffOnly?: boolean;
|
|
952
954
|
}): Promise<Record<string, unknown>>;
|
|
953
955
|
journalAppend(text: string, tags?: string[]): Promise<JournalAppendResult>;
|
|
954
956
|
}
|
package/dist/index.js
CHANGED
|
@@ -1304,7 +1304,8 @@ var RLMClient = class {
|
|
|
1304
1304
|
category: args.category,
|
|
1305
1305
|
outcome: args.outcome || "completed",
|
|
1306
1306
|
files_touched: args.filesTouched || [],
|
|
1307
|
-
persist_types: ["decision", "learning", "workflow"]
|
|
1307
|
+
persist_types: args.persistTypes ?? ["decision", "learning", "workflow"],
|
|
1308
|
+
...args.handoffOnly !== void 0 ? { handoff_only: args.handoffOnly } : {}
|
|
1308
1309
|
});
|
|
1309
1310
|
}
|
|
1310
1311
|
async journalAppend(text, tags) {
|
|
@@ -8234,6 +8235,11 @@ var import_chalk5 = __toESM(require("chalk"));
|
|
|
8234
8235
|
var DEFAULT_SESSION_CONTEXT_TOKENS = 1e3;
|
|
8235
8236
|
var DEFAULT_FULL_WORKFLOW_CRITICAL_TOKENS = 2e3;
|
|
8236
8237
|
var DEFAULT_SHARED_CONTEXT_TOKENS = 2e3;
|
|
8238
|
+
var TASK_COMMIT_TIMEOUT_MS = 3e4;
|
|
8239
|
+
var FINAL_COMMIT_TIMEOUT_MS = 9e4;
|
|
8240
|
+
var FINAL_COMMIT_RETRY_TIMEOUT_MS = 45e3;
|
|
8241
|
+
var FINAL_COMMIT_SUMMARY_MAX_CHARS = 1200;
|
|
8242
|
+
var FINAL_COMMIT_RETRY_SUMMARY_MAX_CHARS = 600;
|
|
8237
8243
|
var SHARED_CONTEXT_INTENT_PATTERN = /\b(standard|standards|convention|conventions|guideline|guidelines|best practice|best practices|policy|policies|compliance|compliant|security rules|team rules|style guide|playbook|checklist)\b/i;
|
|
8238
8244
|
var DOCUMENT_SYNC_FORMATS = {
|
|
8239
8245
|
".adoc": { kind: "DOC", format: "adoc" },
|
|
@@ -8469,6 +8475,52 @@ function toPreview(value, maxLength = 160) {
|
|
|
8469
8475
|
}
|
|
8470
8476
|
return "n/a";
|
|
8471
8477
|
}
|
|
8478
|
+
function positiveIntegerEnv(name, fallback) {
|
|
8479
|
+
const value = process.env[name];
|
|
8480
|
+
if (!value) {
|
|
8481
|
+
return fallback;
|
|
8482
|
+
}
|
|
8483
|
+
const parsed = Number.parseInt(value, 10);
|
|
8484
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
8485
|
+
}
|
|
8486
|
+
function compactWhitespace(value) {
|
|
8487
|
+
return value.replace(/\s+/g, " ").trim();
|
|
8488
|
+
}
|
|
8489
|
+
function truncateText(value, maxLength) {
|
|
8490
|
+
const normalized = compactWhitespace(value);
|
|
8491
|
+
if (normalized.length <= maxLength) {
|
|
8492
|
+
return normalized;
|
|
8493
|
+
}
|
|
8494
|
+
const suffix = " ... [truncated locally for hosted final-commit]";
|
|
8495
|
+
return `${normalized.slice(0, Math.max(0, maxLength - suffix.length)).trimEnd()}${suffix}`;
|
|
8496
|
+
}
|
|
8497
|
+
function buildHostedFinalCommitSummary(args) {
|
|
8498
|
+
const prefix = args.workflowId ? `Workflow ${args.workflowId}
|
|
8499
|
+
Final commit
|
|
8500
|
+
` : "";
|
|
8501
|
+
const budget = Math.max(80, args.maxLength - prefix.length);
|
|
8502
|
+
return `${prefix}${truncateText(args.summary, budget)}`;
|
|
8503
|
+
}
|
|
8504
|
+
function hostedCommitErrorMessage(error) {
|
|
8505
|
+
return error instanceof Error ? error.message : String(error);
|
|
8506
|
+
}
|
|
8507
|
+
function shouldRetryHostedFinalCommit(error) {
|
|
8508
|
+
const message = hostedCommitErrorMessage(error).toLowerCase();
|
|
8509
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
8510
|
+
return true;
|
|
8511
|
+
}
|
|
8512
|
+
return /abort|timeout|timed out|network|fetch|econn|etimedout|http 5\d\d/.test(message);
|
|
8513
|
+
}
|
|
8514
|
+
function isFinalCommitCategory(category) {
|
|
8515
|
+
return Boolean(category?.toLowerCase().includes("final-commit"));
|
|
8516
|
+
}
|
|
8517
|
+
function normalizeFinalCommitCategory(category) {
|
|
8518
|
+
const normalized = compactWhitespace(category ?? "");
|
|
8519
|
+
if (!normalized) {
|
|
8520
|
+
return "final-commit";
|
|
8521
|
+
}
|
|
8522
|
+
return isFinalCommitCategory(normalized) ? normalized : `final-commit:${normalized}`;
|
|
8523
|
+
}
|
|
8472
8524
|
function getPlanStepDisplayTitle(step, index = 0) {
|
|
8473
8525
|
if (typeof step === "string") {
|
|
8474
8526
|
return toPreview(step);
|
|
@@ -12185,7 +12237,7 @@ function printWorkflowTeamSyncResume(result) {
|
|
|
12185
12237
|
}
|
|
12186
12238
|
async function commitTaskMemory(options) {
|
|
12187
12239
|
ensureConfigured();
|
|
12188
|
-
const client = createClient(
|
|
12240
|
+
const client = createClient(TASK_COMMIT_TIMEOUT_MS);
|
|
12189
12241
|
return client.endOfTaskCommit({
|
|
12190
12242
|
summary: options.summary,
|
|
12191
12243
|
category: options.category,
|
|
@@ -12193,6 +12245,101 @@ async function commitTaskMemory(options) {
|
|
|
12193
12245
|
filesTouched: options.files
|
|
12194
12246
|
});
|
|
12195
12247
|
}
|
|
12248
|
+
function recordLocalFinalCommitHandoff(options) {
|
|
12249
|
+
const rootDir = process.cwd();
|
|
12250
|
+
autoArchiveTeamSyncState(rootDir);
|
|
12251
|
+
const state = loadTeamSyncState(rootDir);
|
|
12252
|
+
const record = buildTeamSyncHandoffRecord({
|
|
12253
|
+
summary: truncateText(options.summary, FINAL_COMMIT_SUMMARY_MAX_CHARS),
|
|
12254
|
+
files: options.files,
|
|
12255
|
+
attention: options.outcome === "completed" ? "watch" : "proof",
|
|
12256
|
+
next: options.outcome === "completed" ? "Review this local fallback handoff before starting follow-up work." : "Resolve the blocker captured in this local fallback handoff."
|
|
12257
|
+
});
|
|
12258
|
+
state.handoffs.push(record);
|
|
12259
|
+
state.updatedAt = record.createdAt;
|
|
12260
|
+
saveTeamSyncState(state, rootDir);
|
|
12261
|
+
return {
|
|
12262
|
+
status: "local_fallback",
|
|
12263
|
+
record_id: record.id,
|
|
12264
|
+
state_path: getTeamSyncStatePath(rootDir),
|
|
12265
|
+
category: "team_sync_handoff",
|
|
12266
|
+
source_session_id: "local-companion-fallback",
|
|
12267
|
+
files: record.files,
|
|
12268
|
+
error: options.error
|
|
12269
|
+
};
|
|
12270
|
+
}
|
|
12271
|
+
async function commitFinalTaskMemory(options) {
|
|
12272
|
+
ensureConfigured();
|
|
12273
|
+
const category = normalizeFinalCommitCategory(options.category);
|
|
12274
|
+
const attempts = [];
|
|
12275
|
+
const primarySummary = buildHostedFinalCommitSummary({
|
|
12276
|
+
workflowId: options.workflowId,
|
|
12277
|
+
summary: options.summary,
|
|
12278
|
+
maxLength: FINAL_COMMIT_SUMMARY_MAX_CHARS
|
|
12279
|
+
});
|
|
12280
|
+
const retrySummary = buildHostedFinalCommitSummary({
|
|
12281
|
+
workflowId: options.workflowId,
|
|
12282
|
+
summary: options.summary,
|
|
12283
|
+
maxLength: FINAL_COMMIT_RETRY_SUMMARY_MAX_CHARS
|
|
12284
|
+
});
|
|
12285
|
+
const callHosted = async (summary, timeoutMs) => {
|
|
12286
|
+
const client = createClient(timeoutMs);
|
|
12287
|
+
const handoffOnly = isFinalCommitCategory(category);
|
|
12288
|
+
return client.endOfTaskCommit({
|
|
12289
|
+
summary,
|
|
12290
|
+
category,
|
|
12291
|
+
outcome: options.outcome,
|
|
12292
|
+
filesTouched: options.files,
|
|
12293
|
+
persistTypes: handoffOnly ? [] : ["decision", "learning", "workflow"],
|
|
12294
|
+
handoffOnly
|
|
12295
|
+
});
|
|
12296
|
+
};
|
|
12297
|
+
try {
|
|
12298
|
+
return await callHosted(
|
|
12299
|
+
primarySummary,
|
|
12300
|
+
positiveIntegerEnv("SNIPARA_FINAL_COMMIT_TIMEOUT_MS", FINAL_COMMIT_TIMEOUT_MS)
|
|
12301
|
+
);
|
|
12302
|
+
} catch (error) {
|
|
12303
|
+
attempts.push({
|
|
12304
|
+
summary_chars: primarySummary.length,
|
|
12305
|
+
error: hostedCommitErrorMessage(error)
|
|
12306
|
+
});
|
|
12307
|
+
if (shouldRetryHostedFinalCommit(error)) {
|
|
12308
|
+
try {
|
|
12309
|
+
return await callHosted(
|
|
12310
|
+
retrySummary,
|
|
12311
|
+
positiveIntegerEnv("SNIPARA_FINAL_COMMIT_RETRY_TIMEOUT_MS", FINAL_COMMIT_RETRY_TIMEOUT_MS)
|
|
12312
|
+
);
|
|
12313
|
+
} catch (retryError) {
|
|
12314
|
+
attempts.push({
|
|
12315
|
+
summary_chars: retrySummary.length,
|
|
12316
|
+
error: hostedCommitErrorMessage(retryError)
|
|
12317
|
+
});
|
|
12318
|
+
}
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
const lastError = attempts[attempts.length - 1]?.error ?? "hosted final-commit failed";
|
|
12322
|
+
const localHandoff = recordLocalFinalCommitHandoff({
|
|
12323
|
+
summary: options.summary,
|
|
12324
|
+
outcome: options.outcome,
|
|
12325
|
+
files: options.files,
|
|
12326
|
+
error: lastError
|
|
12327
|
+
});
|
|
12328
|
+
return {
|
|
12329
|
+
stored_count: 0,
|
|
12330
|
+
skipped_count: 0,
|
|
12331
|
+
candidates: [],
|
|
12332
|
+
stored_candidates: [],
|
|
12333
|
+
skipped_candidates: [],
|
|
12334
|
+
team_sync_handoff: localHandoff,
|
|
12335
|
+
hosted_final_commit: {
|
|
12336
|
+
status: "error",
|
|
12337
|
+
attempts,
|
|
12338
|
+
message: "Hosted snipara_end_of_task_commit failed; local workflow state and Team Sync fallback handoff were preserved."
|
|
12339
|
+
},
|
|
12340
|
+
message: "Hosted final-commit failed; local fallback handoff created"
|
|
12341
|
+
};
|
|
12342
|
+
}
|
|
12196
12343
|
function printJournalWarning(result) {
|
|
12197
12344
|
if (result?.status === "error" && result.error) {
|
|
12198
12345
|
console.log(`Journal checkpoint: ${result.error}`);
|
|
@@ -12266,10 +12413,11 @@ async function finalCommitCommand(options) {
|
|
|
12266
12413
|
});
|
|
12267
12414
|
const state = readWorkflowState2();
|
|
12268
12415
|
const outcome = options.outcome ?? "completed";
|
|
12269
|
-
const
|
|
12270
|
-
const result = await
|
|
12271
|
-
|
|
12272
|
-
|
|
12416
|
+
const category = normalizeFinalCommitCategory(options.category);
|
|
12417
|
+
const result = await commitFinalTaskMemory({
|
|
12418
|
+
workflowId: state?.workflowId,
|
|
12419
|
+
summary: options.summary,
|
|
12420
|
+
category,
|
|
12273
12421
|
outcome,
|
|
12274
12422
|
files: options.files
|
|
12275
12423
|
});
|
|
@@ -12279,7 +12427,7 @@ async function finalCommitCommand(options) {
|
|
|
12279
12427
|
state.currentPhaseId = outcome === "completed" ? void 0 : state.currentPhaseId;
|
|
12280
12428
|
state.updatedAt = now;
|
|
12281
12429
|
state.lastCommit = {
|
|
12282
|
-
category
|
|
12430
|
+
category,
|
|
12283
12431
|
outcome,
|
|
12284
12432
|
summary: options.summary,
|
|
12285
12433
|
committedAt: now
|