@useshifu/coding-harness 0.3.2 → 0.3.4
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 +4 -2
- package/bin/shifu-harness.js +146 -33
- package/commands/shifu-sync.md +1 -1
- package/package.json +1 -1
- package/skills/shifu-sync/SKILL.md +4 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Connect installs the local instruction and scheduled runner, saves the connectio
|
|
|
14
14
|
- automatic coding-session sync (manual approval remains available for coding sessions);
|
|
15
15
|
- one input folder created at `~/Desktop/Shifu` on macOS, with a `README.md` explaining what it is for and shortcuts at `~/Downloads/Shifu` and `~/Documents/Shifu` when those parent folders exist.
|
|
16
16
|
|
|
17
|
-
Place `.txt`, `.md`, `.rtf`, or `.docx` files directly in the input folder. Its `README.md` is never synced and an existing one is not overwritten. Screenshots and
|
|
17
|
+
Place `.txt`, `.md`, `.rtf`, or `.docx` files up to 20 MB directly in the input folder. Its `README.md` is never synced and an existing one is not overwritten. Screenshots, images, links, subfolders, and unknown formats are not uploaded; unsupported regular files are counted in sync status. Existing files are picked up on the next scheduled run; unchanged files are not sent again. A failed file stays pending for retry. Folder files never require a claim-review step, even when coding-session sync is in manual mode. Shifu creates claims only when the file supports a specific contribution by the connected user; meeting action items or another person's work are not treated as the user's claims.
|
|
18
18
|
|
|
19
19
|
For named meeting transcripts, set your display name in Shifu Profile so your speaker turns can be identified. If the speaker cannot be matched, uncertain contributions are skipped.
|
|
20
20
|
|
|
@@ -41,7 +41,9 @@ npx @useshifu/coding-harness sync-files --harness codex
|
|
|
41
41
|
|
|
42
42
|
Automatic scheduled runs send each accepted segment and advance its local checkpoint only after the server returns a matching receipt. Manual scheduled runs rebuild an accumulated local queue without advancing checkpoints and create or resume one content-free harness session when the pending count changes; one approval sends the queue in order. Network failures and server throttling are retried, while rejected segments stay pending.
|
|
43
43
|
|
|
44
|
-
The same scheduled run checks the input folder. `sync-files` checks it immediately without changing the schedule. It sends supported file bytes over the configured HTTPS connection (or localhost HTTP for development)
|
|
44
|
+
The same scheduled run checks the input folder. `sync-files` checks it immediately without changing the schedule. Before sending new or changed files, the runner polls unfinished jobs from its local checkpoints. It sends supported file bytes over the configured HTTPS connection (or localhost HTTP for development). The server accepts each file as a background job and processes its text in chunks; upload does not wait for claim extraction. A later scheduled or manual sync polls the job's result. Only `processed`, `no_claims`, or `duplicate` finalizes the local checkpoint; `partial` reopens failed chunks on the next sync. On this upgrade, completed file checkpoints are replayed once so the new extraction can retain suggestions; in-flight jobs are polled first and replayed after finishing. Server-side idempotency prevents already published claims from being duplicated. Files larger than 20 MB remain pending with an error. The scanner reads regular files directly in the chosen folder and ignores symlinks and subfolders. Unknown formats are reported as unsupported rather than treated as successful syncs.
|
|
45
|
+
|
|
46
|
+
The file-job result reports published claims separately from needs-evidence suggestions. A `no_claims` result can still contain suggestions: these are possible contributions awaiting evidence or attribution, not verified claims. The runner reports the suggestion count but never labels those suggestions as established work. It does not print the file's text, candidate statements, or source excerpts to logs. Use Shifu to inspect the suggestions and their evidence status. A result with zero claims and zero suggestions means no user-attributable contribution was extracted; it does not mean the upload failed.
|
|
45
47
|
|
|
46
48
|
The connector captures any useful harness activity, including engineering, product discovery, writing, interview feedback, research, and planning. Version-four payloads support generic `activity` items without a technical scope and accept a safe session-level summary when no item survives redaction. Version 1–3 clients remain supported.
|
|
47
49
|
|
package/bin/shifu-harness.js
CHANGED
|
@@ -41,15 +41,19 @@ const MIN_INTERVAL_HOURS = 0.25;
|
|
|
41
41
|
const MAX_INTERVAL_HOURS = 720;
|
|
42
42
|
const SCHEDULER_TICK_MINUTES = 15;
|
|
43
43
|
const MAX_RETRIES = 4;
|
|
44
|
-
const MAX_WORK_FILE_BYTES =
|
|
45
|
-
const WORK_FILE_SYNC_VERSION =
|
|
44
|
+
const MAX_WORK_FILE_BYTES = 20 * 1024 * 1024;
|
|
45
|
+
const WORK_FILE_SYNC_VERSION = 4;
|
|
46
46
|
const WORK_FILE_EXTENSIONS = new Set([".txt", ".md", ".rtf", ".docx"]);
|
|
47
|
+
const FINAL_WORK_FILE_STATUSES = new Set(["processed", "no_claims", "duplicate"]);
|
|
48
|
+
const ACTIVE_WORK_FILE_STATUSES = new Set(["queued", "processing", "retrying"]);
|
|
47
49
|
const INPUT_FOLDER_README_NAME = "README.md";
|
|
48
50
|
const INPUT_FOLDER_README = `# Shifu sync folder
|
|
49
51
|
|
|
50
52
|
Put work logs, meeting transcripts, and other work documents directly in this folder. Shifu checks it every hour by default and keeps claims only when the file supports a contribution you made. Other people's work and meeting action items alone are not your claims.
|
|
51
53
|
|
|
52
|
-
Supported files: .txt, .md, .rtf, and .docx. Subfolders, links, images, and screenshots are not processed. This README is not synced.
|
|
54
|
+
Supported files: .txt, .md, .rtf, and .docx, up to 20 MB each. Subfolders, links, images, and screenshots are not processed. This README is not synced. Unsupported files are counted but never uploaded.
|
|
55
|
+
|
|
56
|
+
Uploading starts a background job. Shifu checks unfinished jobs before the next scheduled or manual sync; a file is complete only when the server reports its final result. A partial result stays pending for retry. A completed file may have published claims, needs-evidence suggestions, or neither; suggestions are not verified claims.
|
|
53
57
|
|
|
54
58
|
Files you add are sent to Shifu for parsing and sanitization; they are not redacted on this device. Only place files here if you want them processed. For named meeting transcripts, set your display name in Shifu Profile so your speaker turns can be matched.
|
|
55
59
|
`;
|
|
@@ -990,18 +994,25 @@ async function postSync(config, input) {
|
|
|
990
994
|
throw lastError;
|
|
991
995
|
}
|
|
992
996
|
|
|
997
|
+
function scanWorkFiles(folder) {
|
|
998
|
+
const entries = fs.readdirSync(validateInputFolder(folder), { withFileTypes: true })
|
|
999
|
+
.filter((entry) => entry.isFile() && entry.name !== INPUT_FOLDER_README_NAME);
|
|
1000
|
+
return {
|
|
1001
|
+
files: entries.filter((entry) => WORK_FILE_EXTENSIONS.has(path.extname(entry.name).toLowerCase()))
|
|
1002
|
+
.map((entry) => path.join(folder, entry.name)).sort(),
|
|
1003
|
+
unsupported: entries.filter((entry) => !WORK_FILE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())).length,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
993
1007
|
function workFiles(folder) {
|
|
994
|
-
return
|
|
995
|
-
.filter((entry) => entry.isFile() && entry.name !== INPUT_FOLDER_README_NAME && WORK_FILE_EXTENSIONS.has(path.extname(entry.name).toLowerCase()))
|
|
996
|
-
.map((entry) => path.join(folder, entry.name))
|
|
997
|
-
.sort();
|
|
1008
|
+
return scanWorkFiles(folder).files;
|
|
998
1009
|
}
|
|
999
1010
|
|
|
1000
1011
|
function readWorkFile(file) {
|
|
1001
1012
|
const descriptor = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
|
|
1002
1013
|
try {
|
|
1003
1014
|
const before = fs.fstatSync(descriptor);
|
|
1004
|
-
if (!before.isFile() || before.size > MAX_WORK_FILE_BYTES) throw new Error("Only regular files up to
|
|
1015
|
+
if (!before.isFile() || before.size > MAX_WORK_FILE_BYTES) throw new Error("Only regular files up to 20 MB are supported.");
|
|
1005
1016
|
const bytes = fs.readFileSync(descriptor);
|
|
1006
1017
|
const after = fs.fstatSync(descriptor);
|
|
1007
1018
|
if (bytes.length > MAX_WORK_FILE_BYTES || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
|
|
@@ -1021,11 +1032,11 @@ async function postWorkFile(config, file, contents) {
|
|
|
1021
1032
|
const form = new FormData();
|
|
1022
1033
|
form.append("file", new Blob([contents.bytes]), path.basename(file));
|
|
1023
1034
|
form.append("sourceModifiedAt", contents.modifiedAt);
|
|
1024
|
-
response = await fetch(`${config.apiUrl}/v1/connectors/work-files`, {
|
|
1035
|
+
response = await fetch(`${config.apiUrl}/v1/connectors/work-files/jobs`, {
|
|
1025
1036
|
method: "POST",
|
|
1026
1037
|
headers: { Authorization: `Bearer ${config.token}` },
|
|
1027
1038
|
body: form,
|
|
1028
|
-
signal: AbortSignal.timeout(
|
|
1039
|
+
signal: AbortSignal.timeout(2 * 60_000),
|
|
1029
1040
|
});
|
|
1030
1041
|
} catch (error) {
|
|
1031
1042
|
lastError = new Error(`Could not send ${path.basename(file)} to Shifu: ${error?.cause?.code || error?.message || "unknown transport error"}.`);
|
|
@@ -1035,7 +1046,7 @@ async function postWorkFile(config, file, contents) {
|
|
|
1035
1046
|
const payload = await response.json().catch(() => undefined);
|
|
1036
1047
|
if (response.ok) {
|
|
1037
1048
|
const receipt = payload?.data;
|
|
1038
|
-
if (
|
|
1049
|
+
if (validWorkFileJob(receipt)) return receipt;
|
|
1039
1050
|
throw new Error(`Shifu returned an invalid receipt for ${path.basename(file)}. Local state was not advanced.`);
|
|
1040
1051
|
}
|
|
1041
1052
|
lastError = new Error(payload?.error?.message || `Shifu rejected ${path.basename(file)} (${response.status}).`);
|
|
@@ -1045,32 +1056,119 @@ async function postWorkFile(config, file, contents) {
|
|
|
1045
1056
|
throw lastError;
|
|
1046
1057
|
}
|
|
1047
1058
|
|
|
1059
|
+
function validWorkFileJob(receipt) {
|
|
1060
|
+
return typeof receipt?.id === "string" && receipt.id.length > 0 &&
|
|
1061
|
+
(ACTIVE_WORK_FILE_STATUSES.has(receipt.status) || FINAL_WORK_FILE_STATUSES.has(receipt.status) || receipt.status === "partial") &&
|
|
1062
|
+
Number.isInteger(receipt.claimsCreated) && receipt.claimsCreated >= 0 &&
|
|
1063
|
+
(receipt.candidateCount === undefined || (Number.isInteger(receipt.candidateCount) && receipt.candidateCount >= 0));
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function workFileResult(job) {
|
|
1067
|
+
const claims = `${job.claimsCreated} claim${job.claimsCreated === 1 ? "" : "s"}`;
|
|
1068
|
+
const candidates = job.candidateCount > 0 ? `, ${job.candidateCount} needs-evidence suggestion${job.candidateCount === 1 ? "" : "s"}` : "";
|
|
1069
|
+
return `${job.status} (${claims}${candidates})`;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function workFileEntries(state, folder) {
|
|
1073
|
+
if (state.workFiles?.folder !== folder) return {};
|
|
1074
|
+
const files = state.workFiles.files || {};
|
|
1075
|
+
if (state.workFiles.version === WORK_FILE_SYNC_VERSION) return files;
|
|
1076
|
+
// Replay old completed checkpoints once, but never abandon an accepted job.
|
|
1077
|
+
return Object.fromEntries(Object.entries(files)
|
|
1078
|
+
.filter(([, entry]) => entry && typeof entry === "object" && entry.jobId)
|
|
1079
|
+
.map(([name, entry]) => [name, { ...entry, replayOnCompletion: true }]));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
async function getWorkFileJob(config, jobId) {
|
|
1083
|
+
let lastError;
|
|
1084
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {
|
|
1085
|
+
let response;
|
|
1086
|
+
try {
|
|
1087
|
+
response = await fetch(`${config.apiUrl}/v1/connectors/work-files/jobs/${encodeURIComponent(jobId)}`, {
|
|
1088
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
1089
|
+
signal: AbortSignal.timeout(30_000),
|
|
1090
|
+
});
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
lastError = new Error(`Could not check a Shifu file job: ${error?.cause?.code || error?.message || "unknown transport error"}.`);
|
|
1093
|
+
if (attempt + 1 < MAX_RETRIES) await sleep(retryDelay(undefined, attempt));
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
const payload = await response.json().catch(() => undefined);
|
|
1097
|
+
if (response.ok) {
|
|
1098
|
+
if (validWorkFileJob(payload?.data) && payload.data.id === jobId) return payload.data;
|
|
1099
|
+
throw new Error("Shifu returned an invalid file-job status. Local state was not advanced.");
|
|
1100
|
+
}
|
|
1101
|
+
lastError = new Error(payload?.error?.message || `Shifu could not check a file job (${response.status}).`);
|
|
1102
|
+
if (response.status !== 429 && response.status < 500) throw lastError;
|
|
1103
|
+
if (attempt + 1 < MAX_RETRIES) await sleep(retryDelay(response, attempt));
|
|
1104
|
+
}
|
|
1105
|
+
throw lastError;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function saveWorkFileEntry(harness, folder, name, entry) {
|
|
1109
|
+
const state = readState(harness);
|
|
1110
|
+
const files = workFileEntries(state, folder);
|
|
1111
|
+
state.workFiles = { folder, version: WORK_FILE_SYNC_VERSION, files: { ...files, [name]: entry } };
|
|
1112
|
+
saveState(harness, state);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1048
1115
|
async function syncWorkFiles(harness, config = readConfig(harness)) {
|
|
1049
1116
|
if (typeof config.inputFolder !== "string") throw new Error("No input folder is configured. Run connect again to enable file sync.");
|
|
1050
1117
|
const folder = validateInputFolder(configuredInputFolder(config));
|
|
1051
1118
|
const state = readState(harness);
|
|
1052
|
-
const prior = state
|
|
1053
|
-
let
|
|
1054
|
-
|
|
1055
|
-
|
|
1119
|
+
const prior = workFileEntries(state, folder);
|
|
1120
|
+
let submitted = 0;
|
|
1121
|
+
const failedFiles = new Set();
|
|
1122
|
+
const unverifiedJobs = new Set();
|
|
1123
|
+
for (const [name, entry] of Object.entries(prior)) {
|
|
1124
|
+
if (typeof entry !== "object" || !entry?.jobId) continue;
|
|
1125
|
+
try {
|
|
1126
|
+
const job = await getWorkFileJob(config, entry.jobId);
|
|
1127
|
+
if (FINAL_WORK_FILE_STATUSES.has(job.status)) {
|
|
1128
|
+
console.error(`${name}: ${workFileResult(job)}.`);
|
|
1129
|
+
if (entry.replayOnCompletion) {
|
|
1130
|
+
prior[name] = { ...entry, status: "partial" };
|
|
1131
|
+
saveWorkFileEntry(harness, folder, name, prior[name]);
|
|
1132
|
+
} else {
|
|
1133
|
+
saveWorkFileEntry(harness, folder, name, entry.sha256);
|
|
1134
|
+
prior[name] = entry.sha256;
|
|
1135
|
+
}
|
|
1136
|
+
} else {
|
|
1137
|
+
prior[name] = { ...entry, status: job.status };
|
|
1138
|
+
saveWorkFileEntry(harness, folder, name, prior[name]);
|
|
1139
|
+
if (job.status === "partial") console.error(`${name}: partially processed; failed chunks will be retried on this sync.`);
|
|
1140
|
+
}
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
failedFiles.add(name);
|
|
1143
|
+
unverifiedJobs.add(name);
|
|
1144
|
+
console.error(`${name} status remains pending: ${error.message}`);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const scan = scanWorkFiles(folder);
|
|
1148
|
+
if (scan.unsupported) console.error(`${scan.unsupported} unsupported file${scan.unsupported === 1 ? "" : "s"} skipped (only .txt, .md, .rtf, and .docx are uploaded).`);
|
|
1149
|
+
for (const file of scan.files) {
|
|
1056
1150
|
const name = path.basename(file);
|
|
1057
1151
|
try {
|
|
1152
|
+
if (unverifiedJobs.has(name)) continue;
|
|
1058
1153
|
const contents = readWorkFile(file);
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
console.error(`${name}: ${receipt.status} (${receipt.claimsCreated} claim${receipt.claimsCreated === 1 ? "" : "s"}).`);
|
|
1154
|
+
const previous = prior[name];
|
|
1155
|
+
if (previous === contents.sha256 || (previous?.sha256 === contents.sha256 && previous.status !== "partial")) continue;
|
|
1156
|
+
const job = await postWorkFile(config, file, contents);
|
|
1157
|
+
const entry = FINAL_WORK_FILE_STATUSES.has(job.status) ? contents.sha256 : { sha256: contents.sha256, jobId: job.id, status: job.status };
|
|
1158
|
+
saveWorkFileEntry(harness, folder, name, entry);
|
|
1159
|
+
prior[name] = entry;
|
|
1160
|
+
submitted += 1;
|
|
1161
|
+
console.error(`${name}: ${FINAL_WORK_FILE_STATUSES.has(job.status) ? workFileResult(job) : `${job.status} (processing in background)`}.`);
|
|
1068
1162
|
} catch (error) {
|
|
1069
|
-
|
|
1163
|
+
failedFiles.add(name);
|
|
1070
1164
|
console.error(`${name} remains pending: ${error.message}`);
|
|
1071
1165
|
}
|
|
1072
1166
|
}
|
|
1073
|
-
|
|
1167
|
+
const pending = Object.values(prior).filter((entry) => typeof entry === "object" && entry?.jobId).length;
|
|
1168
|
+
for (const [name, entry] of Object.entries(prior)) {
|
|
1169
|
+
if (entry?.status === "partial") failedFiles.add(name);
|
|
1170
|
+
}
|
|
1171
|
+
return { submitted, pending, failures: failedFiles.size, unsupported: scan.unsupported };
|
|
1074
1172
|
}
|
|
1075
1173
|
|
|
1076
1174
|
async function sendSync(harness, input, config = readConfig(harness)) {
|
|
@@ -1223,13 +1321,17 @@ async function scheduledSync(harness) {
|
|
|
1223
1321
|
const startedAt = Date.now();
|
|
1224
1322
|
let failures = 0;
|
|
1225
1323
|
let synced = 0;
|
|
1226
|
-
let
|
|
1324
|
+
let submittedFiles = 0;
|
|
1325
|
+
let pendingFiles = 0;
|
|
1227
1326
|
let failedFiles = 0;
|
|
1327
|
+
let unsupportedFiles = 0;
|
|
1228
1328
|
if (config.inputFolder) {
|
|
1229
1329
|
try {
|
|
1230
1330
|
const files = await syncWorkFiles(harness, config);
|
|
1231
1331
|
failedFiles = files.failures;
|
|
1232
|
-
|
|
1332
|
+
submittedFiles = files.submitted;
|
|
1333
|
+
pendingFiles = files.pending;
|
|
1334
|
+
unsupportedFiles = files.unsupported;
|
|
1233
1335
|
} catch (error) {
|
|
1234
1336
|
failedFiles = 1;
|
|
1235
1337
|
console.error(`Shifu input folder remains pending: ${error.message}`);
|
|
@@ -1256,8 +1358,10 @@ async function scheduledSync(harness) {
|
|
|
1256
1358
|
...state.schedule,
|
|
1257
1359
|
lastAttemptAt: startedAt,
|
|
1258
1360
|
lastResult: notificationError ? "awaiting_approval_notification_failed" : failedFiles ? "partial_failure" : "awaiting_approval",
|
|
1259
|
-
|
|
1361
|
+
submittedFiles,
|
|
1362
|
+
pendingFiles,
|
|
1260
1363
|
failedFiles,
|
|
1364
|
+
unsupportedFiles,
|
|
1261
1365
|
pendingSegments: queue.segments.length,
|
|
1262
1366
|
pendingFingerprint,
|
|
1263
1367
|
approvalSessionRef,
|
|
@@ -1282,8 +1386,10 @@ async function scheduledSync(harness) {
|
|
|
1282
1386
|
lastResult: failures || failedFiles ? "partial_failure" : "complete",
|
|
1283
1387
|
syncedSegments: synced,
|
|
1284
1388
|
failedSessions: failures,
|
|
1285
|
-
|
|
1389
|
+
submittedFiles,
|
|
1390
|
+
pendingFiles,
|
|
1286
1391
|
failedFiles,
|
|
1392
|
+
unsupportedFiles,
|
|
1287
1393
|
nextRunAt: startedAt + (failures || failedFiles ? SCHEDULER_TICK_MINUTES / 60 : policy.intervalHours) * 3_600_000,
|
|
1288
1394
|
};
|
|
1289
1395
|
}
|
|
@@ -1298,14 +1404,21 @@ function status(harness) {
|
|
|
1298
1404
|
const config = readConfig(harness);
|
|
1299
1405
|
const state = readState(harness);
|
|
1300
1406
|
const pending = readJSON(pendingPath(harness), { segments: [] });
|
|
1301
|
-
|
|
1407
|
+
const folder = config.inputFolder ? configuredInputFolder(config) : null;
|
|
1408
|
+
const entries = folder ? Object.values(workFileEntries(state, folder)) : [];
|
|
1409
|
+
let unsupportedFiles = null;
|
|
1410
|
+
let inputFolderError = null;
|
|
1411
|
+
if (folder) {
|
|
1412
|
+
try { unsupportedFiles = scanWorkFiles(folder).unsupported; } catch (error) { inputFolderError = error.message; }
|
|
1413
|
+
}
|
|
1414
|
+
console.log(JSON.stringify({ harness, apiUrl: config.apiUrl, inputFolder: folder, inputFolderError, syncPolicy: normalizedPolicy(config), schedule: state.schedule || null, pendingSegments: pending.segments.length, completedFiles: entries.filter((entry) => typeof entry === "string").length, pendingFiles: entries.filter((entry) => typeof entry === "object" && entry?.jobId).length, unsupportedFiles, sessions: state.sessions }, null, 2));
|
|
1302
1415
|
}
|
|
1303
1416
|
|
|
1304
1417
|
async function syncFilesNow(harness) {
|
|
1305
1418
|
const result = await withSyncLock(harness, () => syncWorkFiles(harness));
|
|
1306
1419
|
if (result?.skipped) throw new Error("Another Shifu sync is already running. Files remain pending.");
|
|
1307
|
-
console.error(`Checked the input folder: ${result.
|
|
1308
|
-
if (result.failures) throw new Error("Some input files
|
|
1420
|
+
console.error(`Checked the input folder: ${result.submitted} submitted, ${result.pending} processing, ${result.failures} needing retry, ${result.unsupported} unsupported.`);
|
|
1421
|
+
if (result.failures) throw new Error("Some input files need retry. They will be checked on the next sync.");
|
|
1309
1422
|
}
|
|
1310
1423
|
|
|
1311
1424
|
function sessions(harness) {
|
package/commands/shifu-sync.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Inspect or sync redacted OpenCode activity using the saved Shifu po
|
|
|
4
4
|
|
|
5
5
|
Run `npx @useshifu/coding-harness status --harness opencode` first.
|
|
6
6
|
|
|
7
|
-
For an explicit Shifu input-folder sync, run `sync-files --harness opencode`. It
|
|
7
|
+
For an explicit Shifu input-folder sync, run `sync-files --harness opencode`. It polls unfinished jobs before submitting new or changed `.txt`, `.md`, `.rtf`, and `.docx` files up to 20 MB. Upload starts asynchronous server processing; check status on the next manual or scheduled sync. Only a final `processed`, `no_claims`, or `duplicate` result completes the local checkpoint; a `partial` result remains retryable. Completed file checkpoints from the previous runner are replayed once; existing in-flight jobs are polled before replay. Report published claims and needs-evidence suggestions separately. `no_claims` may still include suggestions, which are not verified user contributions. Unsupported files are counted but not uploaded. No claim-review step is needed. Do not treat another speaker's work or a meeting action item as the user's contribution. The server parses and sanitizes files; the local runner does not redact file contents, and file text or excerpts must not appear in logs.
|
|
8
8
|
|
|
9
9
|
If manual approval has queued scheduled work, run `pending --harness opencode`, show the complete redacted list, and ask once. After approval, run `approve --approved --harness opencode`.
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ When the user asks to connect, run:
|
|
|
19
19
|
npx @useshifu/coding-harness connect --harness codex
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`connect` installs the current runner and a local schedule with no policy questions. New connections default to hourly automatic sync and `~/Desktop/Shifu` as the single input folder on macOS. It creates shortcuts at `~/Downloads/Shifu` and `~/Documents/Shifu` when possible without replacing conflicting items. Supported files are `.txt`, `.md`, `.rtf`, and `.docx` directly inside the folder; images and screenshots are not supported. If a connection already exists, `connect` keeps its key, checkpoints, and chosen policy. It also excludes the setup session when the harness exposes its current opaque session reference. No content is sent during setup.
|
|
22
|
+
`connect` installs the current runner and a local schedule with no policy questions. New connections default to hourly automatic sync and `~/Desktop/Shifu` as the single input folder on macOS. It creates shortcuts at `~/Downloads/Shifu` and `~/Documents/Shifu` when possible without replacing conflicting items. Supported files are `.txt`, `.md`, `.rtf`, and `.docx` up to 20 MB directly inside the folder; images and screenshots are not supported. Unknown formats are counted but never uploaded. If a connection already exists, `connect` keeps its key, checkpoints, and chosen policy. It also excludes the setup session when the harness exposes its current opaque session reference. No content is sent during setup.
|
|
23
23
|
|
|
24
24
|
When the user asks to change the interval, approval mode, or input folder, run:
|
|
25
25
|
|
|
@@ -33,7 +33,9 @@ Use only the flags the user wants to change; existing settings are preserved. Th
|
|
|
33
33
|
|
|
34
34
|
Run `npx @useshifu/coding-harness status --harness codex` to inspect the saved policy, next scheduled run, pending count, and checkpoints.
|
|
35
35
|
|
|
36
|
-
For an explicit input-folder sync, run `npx @useshifu/coding-harness sync-files --harness codex`. It
|
|
36
|
+
For an explicit input-folder sync, run `npx @useshifu/coding-harness sync-files --harness codex`. It first polls previously accepted file jobs, then submits new or changed supported files. Uploading starts background processing; it does not wait for extraction. A later manual or scheduled sync polls status again. Only `processed`, `no_claims`, or `duplicate` advances a file's completed checkpoint; `partial` is retried on the next sync. The upgraded runner replays old completed checkpoints once, while preserving and polling in-flight jobs before their replay. No claim review or manual approval is needed for files. Report submitted, processing, retry-needed, unsupported, published-claim, and needs-evidence suggestion counts separately. Do not claim a submitted file was fully processed. If a file fails, leave it pending and report the error. Never print file text, candidate statements, or excerpts into logs.
|
|
37
|
+
|
|
38
|
+
The folder runner accepts nonempty `.txt`, `.md`, `.rtf`, and `.docx` files up to 20 MB; the server accepts at most 500,000 extractable characters per file and splits that text into bounded chunks. Empty, malformed, binary, and renamed unsupported files cannot yield claims. A `no_claims` result means processing finished with no published claims; it may still have retained needs-evidence suggestions. Suggestions are possible contributions with unresolved evidence or attribution, not verified claims. If both counts are zero, no attributable contribution was extracted; upload itself may still have succeeded. For a named transcript, confirm the user's Shifu Profile display name matches their speaker label before expecting automated attribution. Never promise a claim from every file or infer one from an action item. The upload has a transport deadline, but model processing has no upload-request timeout; failed chunks back off and can be retried without republishing completed chunks.
|
|
37
39
|
|
|
38
40
|
In manual mode, scheduled runs accumulate redacted segments locally. Run:
|
|
39
41
|
|