snipara-companion 3.0.3 → 3.0.5
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/dist/index.d.ts +18 -0
- package/dist/index.js +324 -46
- package/docs/FULL_REFERENCE.md +5 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1971,6 +1971,7 @@ interface MemoryAuditResult {
|
|
|
1971
1971
|
version: "snipara.memory_audit.v1";
|
|
1972
1972
|
generatedAt: string;
|
|
1973
1973
|
scope?: MemoryScope;
|
|
1974
|
+
summary?: MemoryAuditSummary;
|
|
1974
1975
|
health?: Record<string, unknown>;
|
|
1975
1976
|
cleanCandidates?: Record<string, unknown>;
|
|
1976
1977
|
compactDryRun?: Record<string, unknown>;
|
|
@@ -1979,6 +1980,16 @@ interface MemoryAuditResult {
|
|
|
1979
1980
|
message: string;
|
|
1980
1981
|
}>;
|
|
1981
1982
|
}
|
|
1983
|
+
interface MemoryAuditSummary {
|
|
1984
|
+
totalScanned?: number;
|
|
1985
|
+
activeCount?: number;
|
|
1986
|
+
autoCompactThreshold?: number;
|
|
1987
|
+
autoCompactWouldTrigger?: boolean;
|
|
1988
|
+
cleanupCandidateCounts: Record<string, number>;
|
|
1989
|
+
compactDryRunMutated?: boolean;
|
|
1990
|
+
compactDryRunPlannedActions?: number;
|
|
1991
|
+
recommendedActions: string[];
|
|
1992
|
+
}
|
|
1982
1993
|
declare function memoryHealthCommand(options: MemoryHealthCommandOptions): Promise<void>;
|
|
1983
1994
|
declare function memoryCleanCandidatesCommand(options: MemoryCleanCandidatesCommandOptions): Promise<void>;
|
|
1984
1995
|
declare function memoryCompactCommand(options: MemoryCompactCommandOptions): Promise<void>;
|
|
@@ -2407,6 +2418,7 @@ interface TeamSyncSummary {
|
|
|
2407
2418
|
handoffCount: number;
|
|
2408
2419
|
files: string[];
|
|
2409
2420
|
staleWorkExplanation: TeamSyncStaleWorkExplanation;
|
|
2421
|
+
hygieneActions: TeamSyncHygieneAction[];
|
|
2410
2422
|
latestActiveWork?: TeamSyncWorkRecord;
|
|
2411
2423
|
latestStaleWork?: TeamSyncWorkRecord;
|
|
2412
2424
|
latestCompletedWork?: TeamSyncWorkRecord;
|
|
@@ -2423,6 +2435,12 @@ interface TeamSyncStaleWorkExplanation {
|
|
|
2423
2435
|
latestStaleWorkAgeHours?: number;
|
|
2424
2436
|
message: string;
|
|
2425
2437
|
}
|
|
2438
|
+
interface TeamSyncHygieneAction {
|
|
2439
|
+
kind: "sweep-preview" | "sweep-archive" | "complete-work" | "handoff";
|
|
2440
|
+
command: string;
|
|
2441
|
+
reason: string;
|
|
2442
|
+
mutates: boolean;
|
|
2443
|
+
}
|
|
2426
2444
|
interface TeamSyncCommandOptions {
|
|
2427
2445
|
id?: string;
|
|
2428
2446
|
summary?: string;
|
package/dist/index.js
CHANGED
|
@@ -8826,6 +8826,16 @@ function isRecord2(value) {
|
|
|
8826
8826
|
function numberValue(value) {
|
|
8827
8827
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8828
8828
|
}
|
|
8829
|
+
function numberLikeValue(value) {
|
|
8830
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
8831
|
+
return value;
|
|
8832
|
+
}
|
|
8833
|
+
if (typeof value === "string" && value.trim()) {
|
|
8834
|
+
const parsed = Number(value);
|
|
8835
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
8836
|
+
}
|
|
8837
|
+
return void 0;
|
|
8838
|
+
}
|
|
8829
8839
|
function preview(value, maxLength = 180) {
|
|
8830
8840
|
const text = typeof value === "string" ? value : value === void 0 || value === null ? "" : JSON.stringify(value);
|
|
8831
8841
|
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text;
|
|
@@ -8989,6 +8999,78 @@ function printCompactDryRun(result) {
|
|
|
8989
8999
|
}
|
|
8990
9000
|
}
|
|
8991
9001
|
}
|
|
9002
|
+
function buildMemoryAuditSummary(audit) {
|
|
9003
|
+
const health = isRecord2(audit.health) ? audit.health : {};
|
|
9004
|
+
const healthCounts = isRecord2(health.counts) ? health.counts : {};
|
|
9005
|
+
const healthByStatus = isRecord2(healthCounts.by_status) ? healthCounts.by_status : {};
|
|
9006
|
+
const autoCompact = isRecord2(health.auto_compact) ? health.auto_compact : {};
|
|
9007
|
+
const cleanCandidates = isRecord2(audit.cleanCandidates) ? audit.cleanCandidates : {};
|
|
9008
|
+
const candidateCounts = isRecord2(cleanCandidates.counts) ? cleanCandidates.counts : {};
|
|
9009
|
+
const compactDryRun = isRecord2(audit.compactDryRun) ? audit.compactDryRun : {};
|
|
9010
|
+
const cleanupCandidateCounts = Object.fromEntries(
|
|
9011
|
+
Object.entries(candidateCounts).map(([key, value]) => [key, numberLikeValue(value)]).filter((entry) => entry[1] !== void 0)
|
|
9012
|
+
);
|
|
9013
|
+
const totalScanned = numberLikeValue(health.total_scanned ?? cleanCandidates.total_scanned);
|
|
9014
|
+
const activeCount = numberLikeValue(healthByStatus.active);
|
|
9015
|
+
const autoCompactThreshold = numberLikeValue(autoCompact.threshold);
|
|
9016
|
+
const autoCompactWouldTrigger = autoCompact.would_trigger_by_count === true;
|
|
9017
|
+
const compactDryRunPlannedActions = numberLikeValue(
|
|
9018
|
+
compactDryRun.planned_actions ?? compactDryRun.plannedActions
|
|
9019
|
+
);
|
|
9020
|
+
const recommendedActions = [];
|
|
9021
|
+
const scope = audit.scope ?? "project";
|
|
9022
|
+
if (autoCompactWouldTrigger || activeCount !== void 0 && autoCompactThreshold !== void 0 && activeCount > autoCompactThreshold) {
|
|
9023
|
+
recommendedActions.push(
|
|
9024
|
+
`snipara-companion memory clean-candidates --scope ${scope} --limit-per-bucket 5`
|
|
9025
|
+
);
|
|
9026
|
+
recommendedActions.push(
|
|
9027
|
+
`snipara-companion memory compact --scope ${scope} --archive-older-than-days 30`
|
|
9028
|
+
);
|
|
9029
|
+
}
|
|
9030
|
+
if (Object.values(cleanupCandidateCounts).some((count) => count > 0)) {
|
|
9031
|
+
recommendedActions.push(
|
|
9032
|
+
'snipara-companion memory-guard check --intent "apply memory cleanup" --destructive --strict'
|
|
9033
|
+
);
|
|
9034
|
+
}
|
|
9035
|
+
return {
|
|
9036
|
+
...totalScanned !== void 0 ? { totalScanned } : {},
|
|
9037
|
+
...activeCount !== void 0 ? { activeCount } : {},
|
|
9038
|
+
...autoCompactThreshold !== void 0 ? { autoCompactThreshold } : {},
|
|
9039
|
+
...Object.keys(autoCompact).length > 0 ? { autoCompactWouldTrigger } : {},
|
|
9040
|
+
cleanupCandidateCounts,
|
|
9041
|
+
...typeof compactDryRun.mutated === "boolean" ? { compactDryRunMutated: compactDryRun.mutated } : {},
|
|
9042
|
+
...compactDryRunPlannedActions !== void 0 ? { compactDryRunPlannedActions } : {},
|
|
9043
|
+
recommendedActions: uniqueStrings3(recommendedActions)
|
|
9044
|
+
};
|
|
9045
|
+
}
|
|
9046
|
+
function uniqueStrings3(values) {
|
|
9047
|
+
return Array.from(new Set(values));
|
|
9048
|
+
}
|
|
9049
|
+
function printMemoryAuditSummary(summary) {
|
|
9050
|
+
console.log(import_chalk3.default.bold("Memory Hygiene Summary"));
|
|
9051
|
+
if (summary.totalScanned !== void 0) {
|
|
9052
|
+
console.log(`Scanned: ${summary.totalScanned}`);
|
|
9053
|
+
}
|
|
9054
|
+
if (summary.activeCount !== void 0) {
|
|
9055
|
+
console.log(`Active: ${summary.activeCount}`);
|
|
9056
|
+
}
|
|
9057
|
+
if (summary.autoCompactThreshold !== void 0) {
|
|
9058
|
+
console.log(
|
|
9059
|
+
`Auto-compaction: threshold ${summary.autoCompactThreshold} | would trigger: ${summary.autoCompactWouldTrigger ? "yes" : "no"}`
|
|
9060
|
+
);
|
|
9061
|
+
}
|
|
9062
|
+
if (Object.keys(summary.cleanupCandidateCounts).length > 0) {
|
|
9063
|
+
console.log(
|
|
9064
|
+
`Cleanup candidates: ${Object.entries(summary.cleanupCandidateCounts).map(([key, value]) => `${key}: ${value}`).join(" | ")}`
|
|
9065
|
+
);
|
|
9066
|
+
}
|
|
9067
|
+
if (summary.recommendedActions.length > 0) {
|
|
9068
|
+
console.log("Recommended actions:");
|
|
9069
|
+
for (const action of summary.recommendedActions) {
|
|
9070
|
+
console.log(`- ${action}`);
|
|
9071
|
+
}
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
8992
9074
|
function printMemoryInvalidate(result) {
|
|
8993
9075
|
console.log(import_chalk3.default.bold("Memory Invalidated"));
|
|
8994
9076
|
console.log(`Memory: ${result.memory_id}`);
|
|
@@ -9119,6 +9201,7 @@ async function buildMemoryAudit(options) {
|
|
|
9119
9201
|
message: error instanceof Error ? error.message : String(error)
|
|
9120
9202
|
});
|
|
9121
9203
|
}
|
|
9204
|
+
result.summary = buildMemoryAuditSummary(result);
|
|
9122
9205
|
return result;
|
|
9123
9206
|
}
|
|
9124
9207
|
async function memoryAuditCommand(options) {
|
|
@@ -9132,6 +9215,10 @@ async function memoryAuditCommand(options) {
|
|
|
9132
9215
|
console.log(`Scope: ${audit.scope}`);
|
|
9133
9216
|
}
|
|
9134
9217
|
console.log("");
|
|
9218
|
+
if (audit.summary) {
|
|
9219
|
+
printMemoryAuditSummary(audit.summary);
|
|
9220
|
+
console.log("");
|
|
9221
|
+
}
|
|
9135
9222
|
if (audit.health) {
|
|
9136
9223
|
printMemoryHealth(audit.health);
|
|
9137
9224
|
console.log("");
|
|
@@ -9163,7 +9250,7 @@ async function memoryAuditCommand(options) {
|
|
|
9163
9250
|
var fs12 = __toESM(require("fs"));
|
|
9164
9251
|
var path11 = __toESM(require("path"));
|
|
9165
9252
|
var import_node_child_process2 = require("child_process");
|
|
9166
|
-
function
|
|
9253
|
+
function uniqueStrings4(values) {
|
|
9167
9254
|
return Array.from(new Set((values ?? []).map((value) => value.trim()).filter(Boolean)));
|
|
9168
9255
|
}
|
|
9169
9256
|
function slugify(value, fallback) {
|
|
@@ -9171,7 +9258,7 @@ function slugify(value, fallback) {
|
|
|
9171
9258
|
return slug2 || fallback;
|
|
9172
9259
|
}
|
|
9173
9260
|
function keywordHints(value) {
|
|
9174
|
-
return
|
|
9261
|
+
return uniqueStrings4(
|
|
9175
9262
|
value.split(/[^A-Za-z0-9_.:/-]+/).map((part) => part.trim()).filter((part) => part.length >= 4).slice(0, 8)
|
|
9176
9263
|
);
|
|
9177
9264
|
}
|
|
@@ -9217,17 +9304,17 @@ function expectedItems(section, values, files) {
|
|
|
9217
9304
|
}
|
|
9218
9305
|
function buildEvalCaseArtifact(options = {}) {
|
|
9219
9306
|
const rootDir = path11.resolve(options.dir ?? process.cwd());
|
|
9220
|
-
const files =
|
|
9221
|
-
const commandsRun =
|
|
9307
|
+
const files = uniqueStrings4(options.files);
|
|
9308
|
+
const commandsRun = uniqueStrings4(options.commandRun);
|
|
9222
9309
|
const summary = options.summary?.trim() || "Snipara companion local workflow evaluation";
|
|
9223
9310
|
const id = options.id?.trim() || slugify(summary, "snipara-companion-eval");
|
|
9224
9311
|
const expected = {};
|
|
9225
9312
|
const sectionInputs = {
|
|
9226
|
-
context:
|
|
9227
|
-
decisions:
|
|
9228
|
-
impact:
|
|
9229
|
-
verification:
|
|
9230
|
-
continuity:
|
|
9313
|
+
context: uniqueStrings4(options.context),
|
|
9314
|
+
decisions: uniqueStrings4(options.decision),
|
|
9315
|
+
impact: uniqueStrings4(options.impact),
|
|
9316
|
+
verification: uniqueStrings4(options.verification),
|
|
9317
|
+
continuity: uniqueStrings4(options.continuity)
|
|
9231
9318
|
};
|
|
9232
9319
|
for (const [section, values] of Object.entries(sectionInputs)) {
|
|
9233
9320
|
if (values.length > 0) {
|
|
@@ -9370,7 +9457,7 @@ function detectRuntimeEnvironment(cwd = process.cwd(), env = process.env) {
|
|
|
9370
9457
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
9371
9458
|
const mcpConfigPaths = findRuntimeMcpConfigPaths(cwd, workspaceRoot);
|
|
9372
9459
|
const sandboxCli = detectSandboxCli();
|
|
9373
|
-
const orchestratorCli = detectOrchestratorCli();
|
|
9460
|
+
const orchestratorCli = detectOrchestratorCli(cwd, workspaceRoot);
|
|
9374
9461
|
const parsedVersion = parseRuntimeVersion(sandboxCli.version);
|
|
9375
9462
|
const providerKeys = detectProviderKeys(cwd, workspaceRoot, env);
|
|
9376
9463
|
return {
|
|
@@ -9501,6 +9588,21 @@ function getCommandVersion(command) {
|
|
|
9501
9588
|
return void 0;
|
|
9502
9589
|
}
|
|
9503
9590
|
}
|
|
9591
|
+
function executableExists(filePath) {
|
|
9592
|
+
try {
|
|
9593
|
+
const stat = fs13.statSync(filePath);
|
|
9594
|
+
if (!stat.isFile()) {
|
|
9595
|
+
return false;
|
|
9596
|
+
}
|
|
9597
|
+
if (process.platform === "win32") {
|
|
9598
|
+
return true;
|
|
9599
|
+
}
|
|
9600
|
+
fs13.accessSync(filePath, fs13.constants.X_OK);
|
|
9601
|
+
return true;
|
|
9602
|
+
} catch {
|
|
9603
|
+
return false;
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9504
9606
|
function detectSandboxCli() {
|
|
9505
9607
|
for (const command of SANDBOX_COMMANDS) {
|
|
9506
9608
|
const version = getCommandVersion(command);
|
|
@@ -9514,7 +9616,7 @@ function detectSandboxCli() {
|
|
|
9514
9616
|
}
|
|
9515
9617
|
return {};
|
|
9516
9618
|
}
|
|
9517
|
-
function detectOrchestratorCli() {
|
|
9619
|
+
function detectOrchestratorCli(cwd, workspaceRoot) {
|
|
9518
9620
|
const version = getCommandVersion(ORCHESTRATOR_COMMAND);
|
|
9519
9621
|
if (version || commandExists(ORCHESTRATOR_COMMAND)) {
|
|
9520
9622
|
return {
|
|
@@ -9522,8 +9624,33 @@ function detectOrchestratorCli() {
|
|
|
9522
9624
|
version
|
|
9523
9625
|
};
|
|
9524
9626
|
}
|
|
9627
|
+
for (const candidate of findWorkspaceExecutableCandidates(
|
|
9628
|
+
ORCHESTRATOR_COMMAND,
|
|
9629
|
+
cwd,
|
|
9630
|
+
workspaceRoot
|
|
9631
|
+
)) {
|
|
9632
|
+
if (!executableExists(candidate)) {
|
|
9633
|
+
continue;
|
|
9634
|
+
}
|
|
9635
|
+
return {
|
|
9636
|
+
command: candidate,
|
|
9637
|
+
version: getCommandVersion(candidate)
|
|
9638
|
+
};
|
|
9639
|
+
}
|
|
9525
9640
|
return {};
|
|
9526
9641
|
}
|
|
9642
|
+
function findWorkspaceExecutableCandidates(command, cwd, workspaceRoot) {
|
|
9643
|
+
const executableName = process.platform === "win32" ? `${command}.exe` : command;
|
|
9644
|
+
const roots = Array.from(
|
|
9645
|
+
new Set([workspaceRoot, path12.resolve(cwd)].filter((value) => Boolean(value)))
|
|
9646
|
+
);
|
|
9647
|
+
return roots.flatMap((root) => [
|
|
9648
|
+
path12.join(root, ".venv", "bin", executableName),
|
|
9649
|
+
path12.join(root, "venv", "bin", executableName),
|
|
9650
|
+
path12.join(root, "packages", "agentic-orchestrator", ".venv", "bin", executableName),
|
|
9651
|
+
path12.join(root, "apps", "mcp-server", ".venv", "bin", executableName)
|
|
9652
|
+
]);
|
|
9653
|
+
}
|
|
9527
9654
|
function parseRuntimeVersion(version) {
|
|
9528
9655
|
if (!version) {
|
|
9529
9656
|
return {};
|
|
@@ -10590,7 +10717,7 @@ function normalizeStringList(values) {
|
|
|
10590
10717
|
}
|
|
10591
10718
|
|
|
10592
10719
|
// src/commands/journal.ts
|
|
10593
|
-
function
|
|
10720
|
+
function uniqueStrings5(values) {
|
|
10594
10721
|
if (!values) {
|
|
10595
10722
|
return [];
|
|
10596
10723
|
}
|
|
@@ -10618,7 +10745,7 @@ function buildJournalCheckpointEntry(payload) {
|
|
|
10618
10745
|
payload.actor ? `Actor: ${payload.actor}` : null,
|
|
10619
10746
|
payload.attention ? `Attention: ${payload.attention}` : null,
|
|
10620
10747
|
payload.next ? `Next: ${payload.next}` : null,
|
|
10621
|
-
|
|
10748
|
+
uniqueStrings5(payload.files).length > 0 ? `Files: ${uniqueStrings5(payload.files).join(", ")}` : null
|
|
10622
10749
|
].filter((line) => Boolean(line));
|
|
10623
10750
|
return {
|
|
10624
10751
|
text: lines.join("\n"),
|
|
@@ -10703,6 +10830,13 @@ function buildTeamSyncSummary(state, since, now = /* @__PURE__ */ new Date()) {
|
|
|
10703
10830
|
files.add(file);
|
|
10704
10831
|
}
|
|
10705
10832
|
}
|
|
10833
|
+
const staleWorkExplanation = buildStaleWorkExplanation(
|
|
10834
|
+
work,
|
|
10835
|
+
staleWork,
|
|
10836
|
+
completedWork,
|
|
10837
|
+
archivedWork,
|
|
10838
|
+
now
|
|
10839
|
+
);
|
|
10706
10840
|
return {
|
|
10707
10841
|
activeWorkCount: activeWork.length,
|
|
10708
10842
|
staleWorkCount: staleWork.length,
|
|
@@ -10710,13 +10844,8 @@ function buildTeamSyncSummary(state, since, now = /* @__PURE__ */ new Date()) {
|
|
|
10710
10844
|
archivedWorkCount: archivedWork.length,
|
|
10711
10845
|
handoffCount: handoffs.length,
|
|
10712
10846
|
files: Array.from(files).sort(),
|
|
10713
|
-
staleWorkExplanation
|
|
10714
|
-
|
|
10715
|
-
staleWork,
|
|
10716
|
-
completedWork,
|
|
10717
|
-
archivedWork,
|
|
10718
|
-
now
|
|
10719
|
-
),
|
|
10847
|
+
staleWorkExplanation,
|
|
10848
|
+
hygieneActions: buildTeamSyncHygieneActions(staleWork, staleWorkExplanation),
|
|
10720
10849
|
latestActiveWork: activeWork[activeWork.length - 1],
|
|
10721
10850
|
latestStaleWork: staleWork[staleWork.length - 1],
|
|
10722
10851
|
latestCompletedWork: completedWork[completedWork.length - 1],
|
|
@@ -10724,6 +10853,42 @@ function buildTeamSyncSummary(state, since, now = /* @__PURE__ */ new Date()) {
|
|
|
10724
10853
|
latestHandoff: handoffs[handoffs.length - 1]
|
|
10725
10854
|
};
|
|
10726
10855
|
}
|
|
10856
|
+
function buildTeamSyncHygieneActions(staleWork, explanation) {
|
|
10857
|
+
if (staleWork.length === 0) {
|
|
10858
|
+
return [];
|
|
10859
|
+
}
|
|
10860
|
+
const actions = [];
|
|
10861
|
+
if (explanation.autoArchivableCount > 0) {
|
|
10862
|
+
actions.push({
|
|
10863
|
+
kind: "sweep-preview",
|
|
10864
|
+
command: `snipara-companion team-sync sweep --days ${explanation.autoArchiveAfterDays} --dry-run`,
|
|
10865
|
+
reason: `${explanation.autoArchivableCount} stale active item(s) are old enough to archive.`,
|
|
10866
|
+
mutates: false
|
|
10867
|
+
});
|
|
10868
|
+
actions.push({
|
|
10869
|
+
kind: "sweep-archive",
|
|
10870
|
+
command: `snipara-companion team-sync sweep --days ${explanation.autoArchiveAfterDays}`,
|
|
10871
|
+
reason: "Archive stale local Team Sync work without deleting history.",
|
|
10872
|
+
mutates: true
|
|
10873
|
+
});
|
|
10874
|
+
}
|
|
10875
|
+
const latest = staleWork[staleWork.length - 1];
|
|
10876
|
+
if (latest) {
|
|
10877
|
+
actions.push({
|
|
10878
|
+
kind: "complete-work",
|
|
10879
|
+
command: `snipara-companion team-sync complete-work --id ${latest.id} --next '<completion reason>'`,
|
|
10880
|
+
reason: "Close the latest stale item if the work is actually done.",
|
|
10881
|
+
mutates: true
|
|
10882
|
+
});
|
|
10883
|
+
actions.push({
|
|
10884
|
+
kind: "handoff",
|
|
10885
|
+
command: "snipara-companion team-sync handoff --summary '<current state>' --next '<next action>' --attention watch",
|
|
10886
|
+
reason: "Refresh continuity if the stale work is still active.",
|
|
10887
|
+
mutates: true
|
|
10888
|
+
});
|
|
10889
|
+
}
|
|
10890
|
+
return actions;
|
|
10891
|
+
}
|
|
10727
10892
|
function buildStaleWorkExplanation(work, staleWork, completedWork, archivedWork, now) {
|
|
10728
10893
|
const autoArchivableCount = work.filter(
|
|
10729
10894
|
(item) => item.status === "active" && now.getTime() - getWorkTimestamp(item) > TEAM_SYNC_AUTO_ARCHIVE_WORK_MS
|
|
@@ -10776,8 +10941,12 @@ function completeTeamSyncWorkFromEvidence(state, options = {}) {
|
|
|
10776
10941
|
const workflowGoalText = normalizeCompletionText(options.workflowGoal);
|
|
10777
10942
|
const summaryText = normalizeCompletionText(options.summary);
|
|
10778
10943
|
const evidenceFiles = normalizeFiles(options.files);
|
|
10779
|
-
const
|
|
10780
|
-
|
|
10944
|
+
const activeCandidates = state.work.filter((item) => item.status === "active");
|
|
10945
|
+
const directCandidates = activeCandidates.filter(
|
|
10946
|
+
(item) => matchesDirectCompletionEvidence(item, { workflowGoalText, summaryText })
|
|
10947
|
+
);
|
|
10948
|
+
const candidates = directCandidates.length > 0 ? directCandidates : activeCandidates.filter(
|
|
10949
|
+
(item) => matchesCompletionEvidence(item, { workflowGoalText, summaryText, files: evidenceFiles })
|
|
10781
10950
|
);
|
|
10782
10951
|
if (!options.dryRun) {
|
|
10783
10952
|
for (const item of candidates) {
|
|
@@ -11574,6 +11743,12 @@ function printTeamSyncResult(payload, json) {
|
|
|
11574
11743
|
if (summary.staleWorkCount > 0) {
|
|
11575
11744
|
console.log(`Stale detail: ${summary.staleWorkExplanation.message}`);
|
|
11576
11745
|
}
|
|
11746
|
+
if (summary.hygieneActions.length > 0) {
|
|
11747
|
+
console.log("Hygiene actions:");
|
|
11748
|
+
for (const action2 of summary.hygieneActions.slice(0, 4)) {
|
|
11749
|
+
console.log(`- ${action2.command} (${action2.reason})`);
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11577
11752
|
if (summary.files.length > 0) {
|
|
11578
11753
|
console.log(`Files: ${summary.files.slice(0, 8).join(", ")}`);
|
|
11579
11754
|
}
|
|
@@ -12053,6 +12228,9 @@ function resolveRootDir(dir) {
|
|
|
12053
12228
|
return path15.resolve(dir ?? process.cwd());
|
|
12054
12229
|
}
|
|
12055
12230
|
function matchesCompletionEvidence(item, evidence) {
|
|
12231
|
+
if (matchesDirectCompletionEvidence(item, evidence)) {
|
|
12232
|
+
return true;
|
|
12233
|
+
}
|
|
12056
12234
|
const itemText = normalizeCompletionText(item.summary);
|
|
12057
12235
|
if (!itemText) {
|
|
12058
12236
|
return false;
|
|
@@ -12060,16 +12238,23 @@ function matchesCompletionEvidence(item, evidence) {
|
|
|
12060
12238
|
const evidenceTexts = [evidence.workflowGoalText, evidence.summaryText].filter(
|
|
12061
12239
|
(value) => Boolean(value)
|
|
12062
12240
|
);
|
|
12063
|
-
if (
|
|
12064
|
-
return true;
|
|
12065
|
-
}
|
|
12066
|
-
if (evidence.workflowGoalText) {
|
|
12241
|
+
if (!teamSyncFilesOverlap(item.files, evidence.files)) {
|
|
12067
12242
|
return false;
|
|
12068
12243
|
}
|
|
12069
|
-
|
|
12244
|
+
return evidenceTexts.some((text) => significantTokenOverlap(itemText, text));
|
|
12245
|
+
}
|
|
12246
|
+
function matchesDirectCompletionEvidence(item, evidence) {
|
|
12247
|
+
const itemText = normalizeCompletionText(item.summary);
|
|
12248
|
+
if (!itemText) {
|
|
12070
12249
|
return false;
|
|
12071
12250
|
}
|
|
12072
|
-
|
|
12251
|
+
const evidenceTexts = [evidence.workflowGoalText, evidence.summaryText].filter(
|
|
12252
|
+
(value) => Boolean(value)
|
|
12253
|
+
);
|
|
12254
|
+
if (evidenceTexts.some((text) => completionTextsMatch(itemText, text))) {
|
|
12255
|
+
return true;
|
|
12256
|
+
}
|
|
12257
|
+
return false;
|
|
12073
12258
|
}
|
|
12074
12259
|
function normalizeCompletionText(value) {
|
|
12075
12260
|
const normalized = value?.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -12429,16 +12614,16 @@ function upsertLocalAdaptiveRoutingPolicy(workerRole) {
|
|
|
12429
12614
|
mode: stringValue3(existing.mode) ?? "catalog",
|
|
12430
12615
|
plannerRetainsReasoning: existing.plannerRetainsReasoning !== false,
|
|
12431
12616
|
preferLocalWorkers: true,
|
|
12432
|
-
allowedEndpointTypes:
|
|
12617
|
+
allowedEndpointTypes: uniqueStrings6([
|
|
12433
12618
|
...normalizeStringList2(existing.allowedEndpointTypes),
|
|
12434
12619
|
"local",
|
|
12435
12620
|
"cloud"
|
|
12436
12621
|
]),
|
|
12437
|
-
preferredEndpointTypes:
|
|
12622
|
+
preferredEndpointTypes: uniqueStrings6([
|
|
12438
12623
|
"local",
|
|
12439
12624
|
...normalizeStringList2(existing.preferredEndpointTypes)
|
|
12440
12625
|
]),
|
|
12441
|
-
allowedWorkerClasses:
|
|
12626
|
+
allowedWorkerClasses: uniqueStrings6([
|
|
12442
12627
|
...normalizeStringList2(existing.allowedWorkerClasses),
|
|
12443
12628
|
workerRole,
|
|
12444
12629
|
"documentation",
|
|
@@ -12498,9 +12683,9 @@ function defaultCapabilitiesForWorker(workerRole) {
|
|
|
12498
12683
|
}
|
|
12499
12684
|
function normalizeStringList2(value) {
|
|
12500
12685
|
const values = Array.isArray(value) ? value : value === void 0 ? [] : [value];
|
|
12501
|
-
return
|
|
12686
|
+
return uniqueStrings6(values.map(stringValue3).filter((item) => Boolean(item)));
|
|
12502
12687
|
}
|
|
12503
|
-
function
|
|
12688
|
+
function uniqueStrings6(values) {
|
|
12504
12689
|
return Array.from(new Set(values));
|
|
12505
12690
|
}
|
|
12506
12691
|
function stringValue3(value) {
|
|
@@ -12645,7 +12830,7 @@ function numberValue2(value) {
|
|
|
12645
12830
|
const parsed = Number(text);
|
|
12646
12831
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
12647
12832
|
}
|
|
12648
|
-
function
|
|
12833
|
+
function uniqueStrings7(values) {
|
|
12649
12834
|
return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
|
|
12650
12835
|
}
|
|
12651
12836
|
function normalizeEnum(value) {
|
|
@@ -15255,8 +15440,8 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
|
15255
15440
|
runtimeCatalog: catalog,
|
|
15256
15441
|
routingCard: {
|
|
15257
15442
|
...routing.routingCard,
|
|
15258
|
-
reasons:
|
|
15259
|
-
warnings:
|
|
15443
|
+
reasons: uniqueStrings7(reasons),
|
|
15444
|
+
warnings: uniqueStrings7([...routing.routingCard.warnings, ...gatewayWarnings])
|
|
15260
15445
|
}
|
|
15261
15446
|
};
|
|
15262
15447
|
} catch (error) {
|
|
@@ -15273,7 +15458,7 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
|
15273
15458
|
},
|
|
15274
15459
|
routingCard: {
|
|
15275
15460
|
...routing.routingCard,
|
|
15276
|
-
warnings:
|
|
15461
|
+
warnings: uniqueStrings7([...routing.routingCard.warnings, warning])
|
|
15277
15462
|
}
|
|
15278
15463
|
};
|
|
15279
15464
|
}
|
|
@@ -15439,7 +15624,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
|
|
|
15439
15624
|
const approvalRequired = booleanValue(
|
|
15440
15625
|
isRecord6(resolution?.policyDecision) ? resolution.policyDecision.approvalRequired : void 0
|
|
15441
15626
|
);
|
|
15442
|
-
const gatewayWarnings =
|
|
15627
|
+
const gatewayWarnings = uniqueStrings7([
|
|
15443
15628
|
...resolutionWarnings,
|
|
15444
15629
|
...selectedCandidate ? [] : ["Local orchestrator did not select a worker candidate and will fail closed."]
|
|
15445
15630
|
]);
|
|
@@ -15460,7 +15645,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
|
|
|
15460
15645
|
...routing.routingCard,
|
|
15461
15646
|
...selectedCandidate?.workerClass ? { recommendedWorkerClass: selectedCandidate.workerClass } : {},
|
|
15462
15647
|
...approvalRequired !== void 0 ? { humanApprovalRequired: approvalRequired } : {},
|
|
15463
|
-
reasons:
|
|
15648
|
+
reasons: uniqueStrings7([
|
|
15464
15649
|
...routing.routingCard.reasons,
|
|
15465
15650
|
...resolutionReasons,
|
|
15466
15651
|
...selectedCandidate ? [
|
|
@@ -15469,7 +15654,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
|
|
|
15469
15654
|
...stringValue4(selectedEndpoint?.model) ? [`selected local model ${String(selectedEndpoint?.model)}`] : []
|
|
15470
15655
|
] : ["local orchestrator could not resolve a concrete worker candidate"]
|
|
15471
15656
|
]),
|
|
15472
|
-
warnings:
|
|
15657
|
+
warnings: uniqueStrings7([...routing.routingCard.warnings, ...gatewayWarnings])
|
|
15473
15658
|
}
|
|
15474
15659
|
};
|
|
15475
15660
|
} catch (error) {
|
|
@@ -15486,7 +15671,7 @@ function enrichAdaptiveRoutingWithLocalOrchestrator(routing, options, cwd = proc
|
|
|
15486
15671
|
},
|
|
15487
15672
|
routingCard: {
|
|
15488
15673
|
...routing.routingCard,
|
|
15489
|
-
warnings:
|
|
15674
|
+
warnings: uniqueStrings7([...routing.routingCard.warnings, warning])
|
|
15490
15675
|
}
|
|
15491
15676
|
};
|
|
15492
15677
|
}
|
|
@@ -17289,8 +17474,8 @@ function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = [
|
|
|
17289
17474
|
...routing,
|
|
17290
17475
|
routingCard: {
|
|
17291
17476
|
...routing.routingCard,
|
|
17292
|
-
reasons:
|
|
17293
|
-
warnings:
|
|
17477
|
+
reasons: uniqueStrings7([...routing.routingCard.reasons, ...policyReasons]),
|
|
17478
|
+
warnings: uniqueStrings7([...routing.routingCard.warnings, ...policyWarnings])
|
|
17294
17479
|
}
|
|
17295
17480
|
};
|
|
17296
17481
|
}
|
|
@@ -17306,7 +17491,7 @@ function normalizeAdaptiveRoutingMode(value) {
|
|
|
17306
17491
|
return normalized === "off" || normalized === "recommend" || normalized === "catalog" ? normalized : null;
|
|
17307
17492
|
}
|
|
17308
17493
|
function normalizeAdaptiveWorkerClasses(values) {
|
|
17309
|
-
return
|
|
17494
|
+
return uniqueStrings7((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
|
|
17310
17495
|
(value) => ["documentation", "tests", "review", "coding"].includes(value)
|
|
17311
17496
|
);
|
|
17312
17497
|
}
|
|
@@ -21647,9 +21832,96 @@ function commandForPackageScript(info, scriptName, cwd) {
|
|
|
21647
21832
|
}
|
|
21648
21833
|
return `pnpm ${scriptName}`;
|
|
21649
21834
|
}
|
|
21835
|
+
function shellQuote3(value) {
|
|
21836
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
21837
|
+
}
|
|
21838
|
+
function commandInDirectory(root, cwd, command) {
|
|
21839
|
+
const relativeRoot = import_path4.default.relative(import_path4.default.resolve(cwd), import_path4.default.resolve(root));
|
|
21840
|
+
if (!relativeRoot) {
|
|
21841
|
+
return command;
|
|
21842
|
+
}
|
|
21843
|
+
return `cd ${shellQuote3(relativeRoot)} && ${command}`;
|
|
21844
|
+
}
|
|
21845
|
+
function isPythonSurface(filePath) {
|
|
21846
|
+
const basename7 = import_path4.default.basename(filePath);
|
|
21847
|
+
return filePath.endsWith(".py") || basename7 === "pyproject.toml" || basename7 === "requirements.txt" || basename7.startsWith("requirements-") || basename7 === "setup.py" || basename7 === "setup.cfg" || basename7 === "tox.ini";
|
|
21848
|
+
}
|
|
21849
|
+
function findPyproject(startPath, cwd) {
|
|
21850
|
+
const absoluteStart = import_path4.default.resolve(cwd, startPath);
|
|
21851
|
+
let current = import_fs4.default.existsSync(absoluteStart) && import_fs4.default.statSync(absoluteStart).isDirectory() ? absoluteStart : import_path4.default.dirname(absoluteStart);
|
|
21852
|
+
const stop = import_path4.default.parse(import_path4.default.resolve(cwd)).root;
|
|
21853
|
+
while (current !== stop) {
|
|
21854
|
+
const candidate = import_path4.default.join(current, "pyproject.toml");
|
|
21855
|
+
if (import_fs4.default.existsSync(candidate)) {
|
|
21856
|
+
return candidate;
|
|
21857
|
+
}
|
|
21858
|
+
const parent = import_path4.default.dirname(current);
|
|
21859
|
+
if (parent === current) {
|
|
21860
|
+
break;
|
|
21861
|
+
}
|
|
21862
|
+
current = parent;
|
|
21863
|
+
}
|
|
21864
|
+
return null;
|
|
21865
|
+
}
|
|
21866
|
+
function readPythonProject(pyprojectPath) {
|
|
21867
|
+
try {
|
|
21868
|
+
return {
|
|
21869
|
+
root: import_path4.default.dirname(pyprojectPath),
|
|
21870
|
+
pyprojectPath,
|
|
21871
|
+
text: import_fs4.default.readFileSync(pyprojectPath, "utf8")
|
|
21872
|
+
};
|
|
21873
|
+
} catch {
|
|
21874
|
+
return null;
|
|
21875
|
+
}
|
|
21876
|
+
}
|
|
21877
|
+
function inferPythonChecks(changedFiles, cwd) {
|
|
21878
|
+
const pyprojectFiles = unique4(
|
|
21879
|
+
changedFiles.filter(isPythonSurface).map((file) => findPyproject(file, cwd)).filter((file) => typeof file === "string")
|
|
21880
|
+
);
|
|
21881
|
+
const projects = pyprojectFiles.map(readPythonProject).filter((info) => Boolean(info));
|
|
21882
|
+
return projects.flatMap((info) => {
|
|
21883
|
+
const checks = [
|
|
21884
|
+
{
|
|
21885
|
+
kind: "test",
|
|
21886
|
+
title: "pytest for Python project",
|
|
21887
|
+
command: commandInDirectory(info.root, cwd, "python -m pytest"),
|
|
21888
|
+
source: "fallback",
|
|
21889
|
+
reason: `Detected Python project at ${import_path4.default.relative(cwd, info.pyprojectPath)}.`
|
|
21890
|
+
}
|
|
21891
|
+
];
|
|
21892
|
+
if (/\[tool\.ruff(?:\.|\])/.test(info.text) || /\bruff\b/.test(info.text)) {
|
|
21893
|
+
checks.push({
|
|
21894
|
+
kind: "lint",
|
|
21895
|
+
title: "ruff check for Python project",
|
|
21896
|
+
command: commandInDirectory(info.root, cwd, "python -m ruff check ."),
|
|
21897
|
+
source: "fallback",
|
|
21898
|
+
reason: `Detected Ruff configuration or dependency in ${import_path4.default.relative(cwd, info.pyprojectPath)}.`
|
|
21899
|
+
});
|
|
21900
|
+
}
|
|
21901
|
+
if (/\[tool\.mypy\]/.test(info.text) || /\bmypy\b/.test(info.text)) {
|
|
21902
|
+
checks.push({
|
|
21903
|
+
kind: "type-check",
|
|
21904
|
+
title: "mypy for Python project",
|
|
21905
|
+
command: commandInDirectory(info.root, cwd, "python -m mypy ."),
|
|
21906
|
+
source: "fallback",
|
|
21907
|
+
reason: `Detected mypy configuration or dependency in ${import_path4.default.relative(cwd, info.pyprojectPath)}.`
|
|
21908
|
+
});
|
|
21909
|
+
}
|
|
21910
|
+
if (/\[build-system\]/.test(info.text)) {
|
|
21911
|
+
checks.push({
|
|
21912
|
+
kind: "build",
|
|
21913
|
+
title: "build Python package",
|
|
21914
|
+
command: commandInDirectory(info.root, cwd, "python -m build"),
|
|
21915
|
+
source: "fallback",
|
|
21916
|
+
reason: `Detected Python build system in ${import_path4.default.relative(cwd, info.pyprojectPath)}.`
|
|
21917
|
+
});
|
|
21918
|
+
}
|
|
21919
|
+
return checks;
|
|
21920
|
+
});
|
|
21921
|
+
}
|
|
21650
21922
|
function inferPackageScriptChecks(changedFiles, cwd) {
|
|
21651
21923
|
const packageFiles = unique4(
|
|
21652
|
-
changedFiles.map((file) => findPackageJson(file, cwd)).filter((file) => typeof file === "string")
|
|
21924
|
+
changedFiles.filter((file) => !(isPythonSurface(file) && findPyproject(file, cwd))).map((file) => findPackageJson(file, cwd)).filter((file) => typeof file === "string")
|
|
21653
21925
|
);
|
|
21654
21926
|
const packages = packageFiles.map(readPackageInfo).filter((info) => Boolean(info));
|
|
21655
21927
|
const scriptKinds = [
|
|
@@ -21777,8 +22049,14 @@ function buildVerificationPlan(options) {
|
|
|
21777
22049
|
const impactedFiles = collectImpactedFiles(options.codeImpact, changedFiles, options.filePath);
|
|
21778
22050
|
const directChecks = collectDirectTestChecks(options.codeImpact);
|
|
21779
22051
|
const actionChecks = collectActionChecks(options.codeImpact);
|
|
22052
|
+
const pythonChecks = inferPythonChecks(impactedFiles, cwd);
|
|
21780
22053
|
const packageChecks = inferPackageScriptChecks(impactedFiles, cwd);
|
|
21781
|
-
const recommendedChecks = dedupeChecks([
|
|
22054
|
+
const recommendedChecks = dedupeChecks([
|
|
22055
|
+
...directChecks,
|
|
22056
|
+
...actionChecks,
|
|
22057
|
+
...pythonChecks,
|
|
22058
|
+
...packageChecks
|
|
22059
|
+
]);
|
|
21782
22060
|
const missingChecks = collectCoverageGaps(options.codeImpact);
|
|
21783
22061
|
if (!options.codeImpact) {
|
|
21784
22062
|
missingChecks.push({
|
package/docs/FULL_REFERENCE.md
CHANGED
|
@@ -232,7 +232,10 @@ longer timeout, retries once with a shorter summary on transient hosted failures
|
|
|
232
232
|
and then records a local fallback handoff in `.snipara/team-sync/session.json`
|
|
233
233
|
if the hosted call still times out. A hosted final-commit timeout does not modify
|
|
234
234
|
Git state. Custom final-commit categories are namespaced under `final-commit`
|
|
235
|
-
before the hosted call so they stay on the handoff-only path.
|
|
235
|
+
before the hosted call so they stay on the handoff-only path. Completed workflow
|
|
236
|
+
commits also reconcile local Team Sync work: exact goal/summary matches close
|
|
237
|
+
directly, and slug-like workflow goals can still close the matching active work
|
|
238
|
+
when touched files overlap and meaningful workflow tokens match.
|
|
236
239
|
|
|
237
240
|
## Adaptive Work Routing
|
|
238
241
|
|
|
@@ -961,6 +964,7 @@ Semantics:
|
|
|
961
964
|
- `snipara-companion workflow phase-start` = marks the current phase and prints the required Snipara context gate plus code-impact / symbol-card gates; runtime-marked phases also get a stable Snipara Sandbox session binding
|
|
962
965
|
- `snipara-companion workflow runtime-checkpoint` = captures a resume-ready Snipara Sandbox checkpoint for one phase using local workflow state plus a hosted automation event when configured
|
|
963
966
|
- `snipara-companion workflow phase-commit` = calls hosted `snipara_end_of_task_commit` for that phase, updates local state, and advances the next phase; if the hosted commit times out or hits a transient network failure, local workflow state still advances with an explicit local fallback record
|
|
967
|
+
- `snipara-companion workflow phase-commit` and `workflow final-commit` also complete matching local Team Sync active work when the workflow is completed. Matching is conservative: exact workflow goal/summary text wins, and file overlap plus meaningful token overlap handles slug-like workflow goals without closing unrelated active work.
|
|
964
968
|
- `snipara-companion workflow impact-gate` = local pre-push gate for completed workflow phases in `upstream..HEAD`; it keeps dirty files out of the committed impact analysis and reports phase/file coverage before hosted reindex catches up
|
|
965
969
|
- `snipara-companion workflow resume` = reloads local workflow state plus hosted durable memory after compaction or resume, optionally includes short-lived session context with `--include-session-context`, then appends the latest hosted Team Sync handoff/checkpoint context when available; runtime-bound phases also print a Snipara Sandbox reattach or rehydrate plan; rerun `workflow phase-start` before editing again
|
|
966
970
|
- `snipara-companion workflow resume` does not snapshot or exactly restore a live Snipara Sandbox process; exact process restore remains a roadmap item
|