@useshifu/coding-harness 0.3.2 → 0.3.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 +2 -2
- package/bin/shifu-harness.js +123 -32
- 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,7 @@ 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. Existing file checkpoints are checked once again after upgrading to this job-based protocol. 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
45
|
|
|
46
46
|
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
47
|
|
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 = 3;
|
|
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.
|
|
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,97 @@ 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
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async function getWorkFileJob(config, jobId) {
|
|
1066
|
+
let lastError;
|
|
1067
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {
|
|
1068
|
+
let response;
|
|
1069
|
+
try {
|
|
1070
|
+
response = await fetch(`${config.apiUrl}/v1/connectors/work-files/jobs/${encodeURIComponent(jobId)}`, {
|
|
1071
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
1072
|
+
signal: AbortSignal.timeout(30_000),
|
|
1073
|
+
});
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
lastError = new Error(`Could not check a Shifu file job: ${error?.cause?.code || error?.message || "unknown transport error"}.`);
|
|
1076
|
+
if (attempt + 1 < MAX_RETRIES) await sleep(retryDelay(undefined, attempt));
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const payload = await response.json().catch(() => undefined);
|
|
1080
|
+
if (response.ok) {
|
|
1081
|
+
if (validWorkFileJob(payload?.data) && payload.data.id === jobId) return payload.data;
|
|
1082
|
+
throw new Error("Shifu returned an invalid file-job status. Local state was not advanced.");
|
|
1083
|
+
}
|
|
1084
|
+
lastError = new Error(payload?.error?.message || `Shifu could not check a file job (${response.status}).`);
|
|
1085
|
+
if (response.status !== 429 && response.status < 500) throw lastError;
|
|
1086
|
+
if (attempt + 1 < MAX_RETRIES) await sleep(retryDelay(response, attempt));
|
|
1087
|
+
}
|
|
1088
|
+
throw lastError;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function saveWorkFileEntry(harness, folder, name, entry) {
|
|
1092
|
+
const state = readState(harness);
|
|
1093
|
+
const files = state.workFiles?.folder === folder && state.workFiles.version === WORK_FILE_SYNC_VERSION ? state.workFiles.files || {} : {};
|
|
1094
|
+
state.workFiles = { folder, version: WORK_FILE_SYNC_VERSION, files: { ...files, [name]: entry } };
|
|
1095
|
+
saveState(harness, state);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1048
1098
|
async function syncWorkFiles(harness, config = readConfig(harness)) {
|
|
1049
1099
|
if (typeof config.inputFolder !== "string") throw new Error("No input folder is configured. Run connect again to enable file sync.");
|
|
1050
1100
|
const folder = validateInputFolder(configuredInputFolder(config));
|
|
1051
1101
|
const state = readState(harness);
|
|
1052
1102
|
const prior = state.workFiles?.folder === folder && state.workFiles.version === WORK_FILE_SYNC_VERSION ? state.workFiles.files || {} : {};
|
|
1053
|
-
let
|
|
1054
|
-
|
|
1055
|
-
|
|
1103
|
+
let submitted = 0;
|
|
1104
|
+
const failedFiles = new Set();
|
|
1105
|
+
const unverifiedJobs = new Set();
|
|
1106
|
+
for (const [name, entry] of Object.entries(prior)) {
|
|
1107
|
+
if (typeof entry !== "object" || !entry?.jobId) continue;
|
|
1108
|
+
try {
|
|
1109
|
+
const job = await getWorkFileJob(config, entry.jobId);
|
|
1110
|
+
if (FINAL_WORK_FILE_STATUSES.has(job.status)) {
|
|
1111
|
+
saveWorkFileEntry(harness, folder, name, entry.sha256);
|
|
1112
|
+
prior[name] = entry.sha256;
|
|
1113
|
+
console.error(`${name}: ${job.status} (${job.claimsCreated} claim${job.claimsCreated === 1 ? "" : "s"}).`);
|
|
1114
|
+
} else {
|
|
1115
|
+
prior[name] = { ...entry, status: job.status };
|
|
1116
|
+
saveWorkFileEntry(harness, folder, name, prior[name]);
|
|
1117
|
+
if (job.status === "partial") console.error(`${name}: partially processed; failed chunks will be retried on this sync.`);
|
|
1118
|
+
}
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
failedFiles.add(name);
|
|
1121
|
+
unverifiedJobs.add(name);
|
|
1122
|
+
console.error(`${name} status remains pending: ${error.message}`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
const scan = scanWorkFiles(folder);
|
|
1126
|
+
if (scan.unsupported) console.error(`${scan.unsupported} unsupported file${scan.unsupported === 1 ? "" : "s"} skipped (only .txt, .md, .rtf, and .docx are uploaded).`);
|
|
1127
|
+
for (const file of scan.files) {
|
|
1056
1128
|
const name = path.basename(file);
|
|
1057
1129
|
try {
|
|
1130
|
+
if (unverifiedJobs.has(name)) continue;
|
|
1058
1131
|
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"}).`);
|
|
1132
|
+
const previous = prior[name];
|
|
1133
|
+
if (previous === contents.sha256 || (previous?.sha256 === contents.sha256 && previous.status !== "partial")) continue;
|
|
1134
|
+
const job = await postWorkFile(config, file, contents);
|
|
1135
|
+
const entry = FINAL_WORK_FILE_STATUSES.has(job.status) ? contents.sha256 : { sha256: contents.sha256, jobId: job.id, status: job.status };
|
|
1136
|
+
saveWorkFileEntry(harness, folder, name, entry);
|
|
1137
|
+
prior[name] = entry;
|
|
1138
|
+
submitted += 1;
|
|
1139
|
+
console.error(`${name}: ${job.status}${FINAL_WORK_FILE_STATUSES.has(job.status) ? ` (${job.claimsCreated} claim${job.claimsCreated === 1 ? "" : "s"})` : " (processing in background)"}.`);
|
|
1068
1140
|
} catch (error) {
|
|
1069
|
-
|
|
1141
|
+
failedFiles.add(name);
|
|
1070
1142
|
console.error(`${name} remains pending: ${error.message}`);
|
|
1071
1143
|
}
|
|
1072
1144
|
}
|
|
1073
|
-
|
|
1145
|
+
const pending = Object.values(prior).filter((entry) => typeof entry === "object" && entry?.jobId).length;
|
|
1146
|
+
for (const [name, entry] of Object.entries(prior)) {
|
|
1147
|
+
if (entry?.status === "partial") failedFiles.add(name);
|
|
1148
|
+
}
|
|
1149
|
+
return { submitted, pending, failures: failedFiles.size, unsupported: scan.unsupported };
|
|
1074
1150
|
}
|
|
1075
1151
|
|
|
1076
1152
|
async function sendSync(harness, input, config = readConfig(harness)) {
|
|
@@ -1223,13 +1299,17 @@ async function scheduledSync(harness) {
|
|
|
1223
1299
|
const startedAt = Date.now();
|
|
1224
1300
|
let failures = 0;
|
|
1225
1301
|
let synced = 0;
|
|
1226
|
-
let
|
|
1302
|
+
let submittedFiles = 0;
|
|
1303
|
+
let pendingFiles = 0;
|
|
1227
1304
|
let failedFiles = 0;
|
|
1305
|
+
let unsupportedFiles = 0;
|
|
1228
1306
|
if (config.inputFolder) {
|
|
1229
1307
|
try {
|
|
1230
1308
|
const files = await syncWorkFiles(harness, config);
|
|
1231
1309
|
failedFiles = files.failures;
|
|
1232
|
-
|
|
1310
|
+
submittedFiles = files.submitted;
|
|
1311
|
+
pendingFiles = files.pending;
|
|
1312
|
+
unsupportedFiles = files.unsupported;
|
|
1233
1313
|
} catch (error) {
|
|
1234
1314
|
failedFiles = 1;
|
|
1235
1315
|
console.error(`Shifu input folder remains pending: ${error.message}`);
|
|
@@ -1256,8 +1336,10 @@ async function scheduledSync(harness) {
|
|
|
1256
1336
|
...state.schedule,
|
|
1257
1337
|
lastAttemptAt: startedAt,
|
|
1258
1338
|
lastResult: notificationError ? "awaiting_approval_notification_failed" : failedFiles ? "partial_failure" : "awaiting_approval",
|
|
1259
|
-
|
|
1339
|
+
submittedFiles,
|
|
1340
|
+
pendingFiles,
|
|
1260
1341
|
failedFiles,
|
|
1342
|
+
unsupportedFiles,
|
|
1261
1343
|
pendingSegments: queue.segments.length,
|
|
1262
1344
|
pendingFingerprint,
|
|
1263
1345
|
approvalSessionRef,
|
|
@@ -1282,8 +1364,10 @@ async function scheduledSync(harness) {
|
|
|
1282
1364
|
lastResult: failures || failedFiles ? "partial_failure" : "complete",
|
|
1283
1365
|
syncedSegments: synced,
|
|
1284
1366
|
failedSessions: failures,
|
|
1285
|
-
|
|
1367
|
+
submittedFiles,
|
|
1368
|
+
pendingFiles,
|
|
1286
1369
|
failedFiles,
|
|
1370
|
+
unsupportedFiles,
|
|
1287
1371
|
nextRunAt: startedAt + (failures || failedFiles ? SCHEDULER_TICK_MINUTES / 60 : policy.intervalHours) * 3_600_000,
|
|
1288
1372
|
};
|
|
1289
1373
|
}
|
|
@@ -1298,14 +1382,21 @@ function status(harness) {
|
|
|
1298
1382
|
const config = readConfig(harness);
|
|
1299
1383
|
const state = readState(harness);
|
|
1300
1384
|
const pending = readJSON(pendingPath(harness), { segments: [] });
|
|
1301
|
-
|
|
1385
|
+
const folder = config.inputFolder ? configuredInputFolder(config) : null;
|
|
1386
|
+
const entries = state.workFiles?.version === WORK_FILE_SYNC_VERSION && state.workFiles.folder === folder ? Object.values(state.workFiles.files || {}) : [];
|
|
1387
|
+
let unsupportedFiles = null;
|
|
1388
|
+
let inputFolderError = null;
|
|
1389
|
+
if (folder) {
|
|
1390
|
+
try { unsupportedFiles = scanWorkFiles(folder).unsupported; } catch (error) { inputFolderError = error.message; }
|
|
1391
|
+
}
|
|
1392
|
+
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
1393
|
}
|
|
1303
1394
|
|
|
1304
1395
|
async function syncFilesNow(harness) {
|
|
1305
1396
|
const result = await withSyncLock(harness, () => syncWorkFiles(harness));
|
|
1306
1397
|
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
|
|
1398
|
+
console.error(`Checked the input folder: ${result.submitted} submitted, ${result.pending} processing, ${result.failures} needing retry, ${result.unsupported} unsupported.`);
|
|
1399
|
+
if (result.failures) throw new Error("Some input files need retry. They will be checked on the next sync.");
|
|
1309
1400
|
}
|
|
1310
1401
|
|
|
1311
1402
|
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. 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.
|
|
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. No claim review or manual approval is needed for files. Report submitted, processing, retry-needed, and unsupported counts separately. Do not claim a submitted file was fully processed. If a file fails, leave it pending and report the error.
|
|
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 but no sufficiently grounded contribution could be attributed to the user; it does not mean upload failed. 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
|
|