@useshifu/coding-harness 0.3.4 → 0.3.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/README.md +13 -11
- package/bin/shifu-harness.js +146 -33
- package/commands/shifu-sync.md +3 -3
- package/package.json +1 -1
- package/skills/shifu-sync/SKILL.md +13 -11
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
## Connect
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npx @useshifu/coding-harness connect --harness <codex|claude_code|opencode>
|
|
8
|
+
npx --yes @useshifu/coding-harness connect --harness <codex|claude_code|opencode>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Connect installs the local instruction and scheduled runner, saves the connection key with owner-only permissions, and uses these defaults without setup questions:
|
|
@@ -20,23 +20,25 @@ For named meeting transcripts, set your display name in Shifu Profile so your sp
|
|
|
20
20
|
|
|
21
21
|
The shortcuts are symlinks to the same folder, not additional watched folders. An existing conflicting `Shifu` item is left untouched and reported. To choose another existing, readable folder, use `--input-folder <path>` with `connect` or `config update`; Shifu checks the path before saving the change. No macOS permission flow is required by the CLI.
|
|
22
22
|
|
|
23
|
-
No content is sent during setup.
|
|
23
|
+
No content is sent during setup. Every `connect` asks for a connection key and verifies it with Shifu for the chosen coding tool before saving it or reporting success. An invalid key or unreachable server leaves the existing connection unchanged. Replacing a key resets local checkpoints so existing work can be reconsidered for the new connection; the selected sync policy remains. If a coding tool cannot run `npx` or accept hidden input in its tool terminal, run the same command in your own interactive terminal and check the confirmation output. Change the choices later with:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
npx @useshifu/coding-harness config update --harness <codex|claude_code|opencode> --interval-hours 1 --approval-mode automatic --input-folder ~/Desktop/Shifu
|
|
26
|
+
npx --yes @useshifu/coding-harness config update --harness <codex|claude_code|opencode> --interval-hours 1 --approval-mode automatic --input-folder ~/Desktop/Shifu
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Older configuration files remain on-demand and manual until either command completes onboarding; upgrading the package alone never enables background upload.
|
|
30
30
|
|
|
31
|
+
To disconnect this device, run `npx --yes @useshifu/coding-harness disconnect --harness <codex|claude_code|opencode>`. This removes the local key, schedule, and checkpoints. It does not revoke the key on Shifu; revoke the old key there if it should no longer work.
|
|
32
|
+
|
|
31
33
|
## Operate
|
|
32
34
|
|
|
33
35
|
```sh
|
|
34
|
-
npx @useshifu/coding-harness status --harness codex
|
|
35
|
-
npx @useshifu/coding-harness sessions --harness codex --hours 24
|
|
36
|
-
npx @useshifu/coding-harness review --harness codex --session-ref opaque-session-id
|
|
37
|
-
npx @useshifu/coding-harness pending --harness codex
|
|
38
|
-
npx @useshifu/coding-harness approve --harness codex
|
|
39
|
-
npx @useshifu/coding-harness sync-files --harness codex
|
|
36
|
+
npx --yes @useshifu/coding-harness status --harness codex
|
|
37
|
+
npx --yes @useshifu/coding-harness sessions --harness codex --hours 24
|
|
38
|
+
npx --yes @useshifu/coding-harness review --harness codex --session-ref opaque-session-id
|
|
39
|
+
npx --yes @useshifu/coding-harness pending --harness codex
|
|
40
|
+
npx --yes @useshifu/coding-harness approve --harness codex
|
|
41
|
+
npx --yes @useshifu/coding-harness sync-files --harness codex
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
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.
|
|
@@ -45,12 +47,12 @@ The same scheduled run checks the input folder. `sync-files` checks it immediate
|
|
|
45
47
|
|
|
46
48
|
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.
|
|
47
49
|
|
|
48
|
-
The connector captures
|
|
50
|
+
The connector captures supported harness work in engineering, product discovery, writing, interview feedback, research, and planning. Automatic sync turns completed actions and known work topics into separate controlled statements without sending source prose. If a segment has no supported work items, it advances the checkpoint without creating a claim. Version 1–3 clients remain supported.
|
|
49
51
|
|
|
50
52
|
## Sync a prepared payload
|
|
51
53
|
|
|
52
54
|
```sh
|
|
53
|
-
printf '%s' '{"harness":"codex","sessionRef":"opaque-session-id","fromTurn":1,"toTurn":3,"title":"Prepared structured interview feedback","summary":"Prepared structured interview feedback and clarified the recommendation.","evidence":[{"kind":"activity","title":"Drafted interview feedback","statement":"Prepared structured interview feedback."}],"verification":[],"decisions":[],"redactionVersion":4}' | npx @useshifu/coding-harness sync --harness codex
|
|
55
|
+
printf '%s' '{"harness":"codex","sessionRef":"opaque-session-id","fromTurn":1,"toTurn":3,"title":"Prepared structured interview feedback","summary":"Prepared structured interview feedback and clarified the recommendation.","evidence":[{"kind":"activity","title":"Drafted interview feedback","statement":"Prepared structured interview feedback."}],"verification":[],"decisions":[],"redactionVersion":4}' | npx --yes @useshifu/coding-harness sync --harness codex
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
Automatic mode sends immediately. Manual mode asks once unless the calling harness already obtained approval and passes `--approved`.
|
package/bin/shifu-harness.js
CHANGED
|
@@ -543,41 +543,77 @@ function conciseTitle(statement, fallback) {
|
|
|
543
543
|
return `${value.slice(0, MAX_TITLE_LENGTH - 1).trimEnd()}…`;
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
-
const
|
|
547
|
-
{ key: "
|
|
548
|
-
{ key: "
|
|
549
|
-
{ key: "
|
|
550
|
-
{ key: "
|
|
551
|
-
{ key: "
|
|
552
|
-
{ key: "
|
|
553
|
-
{ key: "
|
|
554
|
-
{ key: "
|
|
546
|
+
const WORK_ACTIONS = [
|
|
547
|
+
{ key: "implemented", title: "Implemented", pattern: /^(?:I\s+|We\s+)?(?:implemented|added|built|shipped)\b/i },
|
|
548
|
+
{ key: "fixed", title: "Fixed", pattern: /^(?:I\s+|We\s+)?(?:fixed|resolved|corrected)\b/i },
|
|
549
|
+
{ key: "improved", title: "Improved", pattern: /^(?:I\s+|We\s+)?(?:improved|optimized|updated)\b/i },
|
|
550
|
+
{ key: "refactored", title: "Refactored", pattern: /^(?:I\s+|We\s+)?refactored\b/i },
|
|
551
|
+
{ key: "designed", title: "Designed", pattern: /^(?:I\s+|We\s+)?designed\b/i },
|
|
552
|
+
{ key: "prepared", title: "Prepared", pattern: /^(?:I\s+|We\s+)?(?:prepared|drafted|wrote)\b/i },
|
|
553
|
+
{ key: "reviewed", title: "Reviewed", pattern: /^(?:I\s+|We\s+)?(?:reviewed|audited)\b/i },
|
|
554
|
+
{ key: "investigated", title: "Investigated", pattern: /^(?:I\s+|We\s+)?(?:investigated|researched|evaluated|compared)\b/i },
|
|
555
|
+
{ key: "deployed", title: "Deployed", pattern: /^(?:I\s+|We\s+)?(?:deployed|published)\b/i },
|
|
555
556
|
];
|
|
556
557
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
558
|
+
const WORK_OBJECTS = [
|
|
559
|
+
{ key: "interview_feedback", title: "interview feedback", pattern: /\b(?:interview feedback|candidate feedback|hiring feedback)\b/i },
|
|
560
|
+
{ key: "professional_post", title: "professional writing", pattern: /\b(?:linkedin|social post|professional post|newsletter|article)\b/i },
|
|
561
|
+
{ key: "home_review", title: "Home review", pattern: /\b(?:home review|work review|review on the home page)\b/i },
|
|
562
|
+
{ key: "connection_key", title: "connection key validation", pattern: /\b(?:connection key|connector key|key validation)\b/i },
|
|
563
|
+
{ key: "claim_extraction", title: "claim extraction", pattern: /\b(?:claim extraction|claim generation|claim matching)\b/i },
|
|
564
|
+
{ key: "retry", title: "retry handling", pattern: /\b(?:retries|retry)\b/i },
|
|
565
|
+
{ key: "cache", title: "cache behavior", pattern: /\b(?:cache|caching|ttl)\b/i },
|
|
566
|
+
{ key: "navigation", title: "navigation", pattern: /\b(?:navigation|sidebar|sub.?nav)\b/i },
|
|
567
|
+
{ key: "sync", title: "sync behavior", pattern: /\b(?:sync|synchronization|checkpoint)\b/i },
|
|
568
|
+
{ key: "auth", title: "authentication", pattern: /\b(?:authentication|sign.?in|login)\b/i },
|
|
569
|
+
{ key: "permissions", title: "access controls", pattern: /\b(?:authorization|permissions?|access control)\b/i },
|
|
570
|
+
{ key: "database", title: "database migration", pattern: /\b(?:database migration|schema migration|postgres migration)\b/i },
|
|
571
|
+
{ key: "api", title: "API endpoint", pattern: /\b(?:api|endpoint|http route)\b/i },
|
|
572
|
+
{ key: "frontend", title: "frontend component", pattern: /\b(?:frontend|react component|ui component|user interface)\b/i },
|
|
573
|
+
{ key: "test", title: "test coverage", pattern: /\b(?:test|tests|test suite|coverage)\b/i },
|
|
574
|
+
{ key: "docs", title: "documentation", pattern: /\b(?:documentation|readme|guide|runbook)\b/i },
|
|
575
|
+
{ key: "deploy", title: "deployment pipeline", pattern: /\b(?:deployment|deploy pipeline|ci pipeline|release pipeline)\b/i },
|
|
576
|
+
{ key: "performance", title: "performance", pattern: /\b(?:performance|latency|throughput)\b/i },
|
|
577
|
+
{ key: "security", title: "security controls", pattern: /\b(?:security|vulnerability|secret handling)\b/i },
|
|
578
|
+
{ key: "research", title: "product research", pattern: /\b(?:product discovery|user research|customer research)\b/i },
|
|
579
|
+
{ key: "planning", title: "project plan", pattern: /\b(?:roadmap|project plan|delivery plan)\b/i },
|
|
580
|
+
];
|
|
581
|
+
|
|
582
|
+
function safeActivityEvidence(notes) {
|
|
583
|
+
const evidence = [];
|
|
584
|
+
const seen = new Set();
|
|
585
|
+
for (const [noteIndex, { turn, note }] of notes.entries()) {
|
|
586
|
+
let inCodeBlock = false;
|
|
587
|
+
for (const [lineIndex, rawLine] of note.split("\n").entries()) {
|
|
588
|
+
if (rawLine.trim().startsWith("```")) { inCodeBlock = !inCodeBlock; continue; }
|
|
589
|
+
if (inCodeBlock) continue;
|
|
590
|
+
const line = rawLine.trim().replace(/^[-*]\s+/, "").split(/[.;—]/, 1)[0];
|
|
591
|
+
const action = WORK_ACTIONS.find((item) => item.pattern.test(line));
|
|
592
|
+
const object = WORK_OBJECTS.find((item) => item.pattern.test(line));
|
|
593
|
+
if (!action || !object) continue;
|
|
594
|
+
const key = `${action.key}:${object.key}`;
|
|
595
|
+
if (seen.has(key)) continue;
|
|
596
|
+
seen.add(key);
|
|
597
|
+
const title = `${action.title} ${object.title}`;
|
|
598
|
+
evidence.push({ kind: "activity", title, statement: `${title}.`, itemRef: `t${turn}_n${noteIndex + 1}_l${lineIndex + 1}` });
|
|
599
|
+
if (evidence.length === MAX_EVIDENCE_ITEMS) return evidence;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return evidence;
|
|
567
603
|
}
|
|
568
604
|
|
|
569
605
|
function payloadForSegment(harness, segment) {
|
|
570
606
|
const { notes } = reviewSegment(harness, segment.sessionRef, segment.fromTurn, segment.toTurn);
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
|
|
607
|
+
const evidence = safeActivityEvidence(notes);
|
|
608
|
+
const title = evidence.length ? evidence[0].title : "No supported work identified";
|
|
609
|
+
const summary = evidence.length > 1 ? `${evidence[0].statement} ${evidence.length - 1} more supported work items.` :
|
|
610
|
+
evidence.length === 1 ? evidence[0].statement : "This range had no supported work items.";
|
|
575
611
|
return {
|
|
576
612
|
harness,
|
|
577
613
|
sessionRef: segment.sessionRef,
|
|
578
614
|
fromTurn: segment.fromTurn,
|
|
579
615
|
toTurn: segment.toTurn,
|
|
580
|
-
title: conciseTitle(title,
|
|
616
|
+
title: conciseTitle(title, "No supported work identified"),
|
|
581
617
|
summary,
|
|
582
618
|
evidence,
|
|
583
619
|
verification: [],
|
|
@@ -667,7 +703,12 @@ async function confirm(question) {
|
|
|
667
703
|
|
|
668
704
|
async function readSecret(input = process.stdin) {
|
|
669
705
|
const ownsInput = !input.isTTY;
|
|
670
|
-
if (ownsInput)
|
|
706
|
+
if (ownsInput) {
|
|
707
|
+
let descriptor;
|
|
708
|
+
try { descriptor = fs.openSync(process.platform === "win32" ? "CONIN$" : "/dev/tty", "r"); }
|
|
709
|
+
catch { throw new Error("This terminal cannot accept a hidden connection key. Run connect in your own interactive terminal."); }
|
|
710
|
+
input = new tty.ReadStream(descriptor);
|
|
711
|
+
}
|
|
671
712
|
const wasRaw = input.isRaw;
|
|
672
713
|
process.stderr.write("Connection key (hidden): ");
|
|
673
714
|
input.setRawMode(true);
|
|
@@ -755,11 +796,11 @@ function install(harness, quiet = false) {
|
|
|
755
796
|
.replaceAll('"codex"', '"opencode"')
|
|
756
797
|
.replaceAll("--harness codex", "--harness opencode")
|
|
757
798
|
.replaceAll("Codex", "OpenCode")
|
|
758
|
-
.replaceAll("npx @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
799
|
+
.replaceAll("npx --yes @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
759
800
|
fs.writeFileSync(path.join(skillDest, "SKILL.md"), skillContent, { mode: 0o600 });
|
|
760
801
|
|
|
761
802
|
const commandContent = fs.readFileSync(path.join(__dirname, "..", "commands", "shifu-sync.md"), "utf8")
|
|
762
|
-
.replaceAll("npx @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
803
|
+
.replaceAll("npx --yes @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
763
804
|
for (const folder of ["command", "commands"]) {
|
|
764
805
|
const commandDir = path.join(configDir, "opencode", folder);
|
|
765
806
|
fs.mkdirSync(commandDir, { recursive: true, mode: 0o700 });
|
|
@@ -773,14 +814,14 @@ function install(harness, quiet = false) {
|
|
|
773
814
|
.replaceAll('"codex"', `"${harness}"`)
|
|
774
815
|
.replaceAll("--harness codex", `--harness ${harness}`)
|
|
775
816
|
.replaceAll("Codex", HARNESS_NAMES[harness])
|
|
776
|
-
.replaceAll("npx @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
817
|
+
.replaceAll("npx --yes @useshifu/coding-harness", `"${process.execPath}" "${installedRunnerPath()}"`);
|
|
777
818
|
fs.writeFileSync(skill, content, { mode: 0o600 });
|
|
778
819
|
}
|
|
779
820
|
const removedHooks = harness === "codex" && removeCodexHooks();
|
|
780
821
|
if (!quiet) {
|
|
781
822
|
console.error(`Installed the Shifu sync instruction for ${HARNESS_NAMES[harness]}.`);
|
|
782
823
|
if (removedHooks) console.error("Removed obsolete Codex lifecycle hooks.");
|
|
783
|
-
console.error(`Next: coding-harness connect --harness ${harness}`);
|
|
824
|
+
console.error(`Next: npx --yes @useshifu/coding-harness connect --harness ${harness}`);
|
|
784
825
|
}
|
|
785
826
|
}
|
|
786
827
|
|
|
@@ -849,6 +890,45 @@ function installSchedule(harness) {
|
|
|
849
890
|
return artifact.file;
|
|
850
891
|
}
|
|
851
892
|
|
|
893
|
+
function removeSchedule(harness) {
|
|
894
|
+
const artifact = schedulerArtifact(harness, process.env.SHIFU_PLATFORM || process.platform);
|
|
895
|
+
if (process.env.SHIFU_SKIP_SCHEDULER_ACTIVATION !== "1") {
|
|
896
|
+
if (process.platform === "darwin") {
|
|
897
|
+
const domain = `gui/${process.getuid()}`;
|
|
898
|
+
const label = `com.useshifu.coding-harness.${harness}`;
|
|
899
|
+
const scheduleIsLoaded = () => {
|
|
900
|
+
let listing;
|
|
901
|
+
try { listing = execFileSync("launchctl", ["list"], { encoding: "utf8" }); }
|
|
902
|
+
catch { throw new Error("Could not check the Shifu schedule. Disconnect was cancelled."); }
|
|
903
|
+
return listing.split(/\r?\n/).some((line) => line.trim().split(/\s+/).at(-1) === label);
|
|
904
|
+
};
|
|
905
|
+
if (scheduleIsLoaded()) {
|
|
906
|
+
try { execFileSync("launchctl", ["bootout", domain, artifact.file], { stdio: "ignore" }); }
|
|
907
|
+
catch { throw new Error("Could not stop the Shifu schedule. Disconnect was cancelled."); }
|
|
908
|
+
if (scheduleIsLoaded()) throw new Error("The Shifu schedule is still running. Disconnect was cancelled.");
|
|
909
|
+
}
|
|
910
|
+
} else if (process.platform === "linux") {
|
|
911
|
+
if (fs.existsSync(artifact.file)) {
|
|
912
|
+
const timer = `shifu-coding-harness-${harness}.timer`;
|
|
913
|
+
try { execFileSync("systemctl", ["--user", "disable", "--now", timer], { stdio: "ignore" }); }
|
|
914
|
+
catch { throw new Error("Could not stop the Shifu schedule. Disconnect was cancelled."); }
|
|
915
|
+
let state;
|
|
916
|
+
try { state = execFileSync("systemctl", ["--user", "show", "--property=ActiveState", "--value", timer], { encoding: "utf8" }).trim(); }
|
|
917
|
+
catch { throw new Error("Could not verify that the Shifu schedule stopped. Disconnect was cancelled."); }
|
|
918
|
+
if (state === "active" || state === "activating") throw new Error("The Shifu schedule is still running. Disconnect was cancelled.");
|
|
919
|
+
}
|
|
920
|
+
} else if (process.platform === "win32") {
|
|
921
|
+
if (fs.existsSync(artifact.file)) {
|
|
922
|
+
try { execFileSync("schtasks.exe", ["/Delete", "/TN", `Shifu coding harness ${harness}`, "/F"], { stdio: "ignore" }); }
|
|
923
|
+
catch { throw new Error("Could not stop the Shifu schedule. Disconnect was cancelled."); }
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
for (const item of [artifact, ...(artifact.extraFiles || [])]) {
|
|
928
|
+
try { fs.unlinkSync(item.file); } catch (error) { if (error?.code !== "ENOENT") throw error; }
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
852
932
|
function scheduleNextRun(harness, intervalHours) {
|
|
853
933
|
const state = readState(harness);
|
|
854
934
|
state.schedule = { ...(state.schedule || {}), nextRunAt: Date.now() + intervalHours * 3_600_000 };
|
|
@@ -864,11 +944,28 @@ async function chooseSyncPolicy(existing) {
|
|
|
864
944
|
};
|
|
865
945
|
}
|
|
866
946
|
|
|
867
|
-
async function
|
|
947
|
+
async function validateConnection(apiUrl, token, harness) {
|
|
948
|
+
let response;
|
|
949
|
+
try {
|
|
950
|
+
response = await fetch(`${apiUrl}/v1/connectors/coding-sessions/connection?harness=${encodeURIComponent(harness)}`, {
|
|
951
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
952
|
+
signal: AbortSignal.timeout(10_000),
|
|
953
|
+
});
|
|
954
|
+
} catch {
|
|
955
|
+
throw new Error("Could not reach Shifu to verify the connection key. Nothing was changed.");
|
|
956
|
+
}
|
|
957
|
+
if (response.status === 401 || response.status === 422) throw new Error("Shifu rejected this connection key for the selected coding tool. Nothing was changed.");
|
|
958
|
+
if (!response.ok) throw new Error(`Shifu could not verify the connection key (HTTP ${response.status}). Nothing was changed.`);
|
|
959
|
+
const result = await response.json().catch(() => null);
|
|
960
|
+
if (result?.data?.connected !== true) throw new Error("Shifu returned an invalid connection check. Nothing was changed.");
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
async function connect(harness, readKey = readSecret) {
|
|
868
964
|
const existing = readJSON(configPath(harness), undefined);
|
|
869
|
-
const token =
|
|
965
|
+
const token = (await readKey()).trim();
|
|
870
966
|
if (!TOKEN_PATTERN.test(token)) throw new Error("The connection key is invalid.");
|
|
871
967
|
const apiUrl = requireSafeApiUrl(option("--api-url") || existing?.apiUrl || process.env.SHIFU_API_URL || DEFAULT_API_URL);
|
|
968
|
+
await validateConnection(apiUrl, token, harness);
|
|
872
969
|
const savedPolicy = existing && normalizedPolicy(existing);
|
|
873
970
|
const currentPolicy = savedPolicy?.onboardingComplete ? savedPolicy : { intervalHours: DEFAULT_INTERVAL_HOURS, approvalMode: "automatic", excludedSessionRefs: savedPolicy?.excludedSessionRefs || [] };
|
|
874
971
|
const selected = await chooseSyncPolicy(currentPolicy);
|
|
@@ -878,6 +975,11 @@ async function connect(harness) {
|
|
|
878
975
|
install(harness, true);
|
|
879
976
|
const scheduleFile = installSchedule(harness);
|
|
880
977
|
writeJSON(configPath(harness), { ...existing, version: CONFIG_VERSION, apiUrl, token, harness, syncPolicy, inputFolder });
|
|
978
|
+
if (existing?.token && existing.token !== token) {
|
|
979
|
+
for (const file of [statePath(harness), pendingPath(harness)]) {
|
|
980
|
+
try { fs.unlinkSync(file); } catch (error) { if (error?.code !== "ENOENT") throw error; }
|
|
981
|
+
}
|
|
982
|
+
}
|
|
881
983
|
scheduleNextRun(harness, syncPolicy.intervalHours);
|
|
882
984
|
console.error(`${HARNESS_NAMES[harness]} is connected. It will sync every ${syncPolicy.intervalHours} hours with ${syncPolicy.approvalMode} approval.`);
|
|
883
985
|
console.error(`Shifu sync folder: ${inputFolder}. Place .txt, .md, .rtf, or .docx files there. Only grounded contributions will become claims.`);
|
|
@@ -885,6 +987,16 @@ async function connect(harness) {
|
|
|
885
987
|
console.error(`Schedule installed at ${scheduleFile}. No content was sent during setup.`);
|
|
886
988
|
}
|
|
887
989
|
|
|
990
|
+
function disconnect(harness) {
|
|
991
|
+
readConfig(harness);
|
|
992
|
+
if (fs.existsSync(lockPath(harness))) throw new Error("A Shifu sync is running. Try disconnecting again after it finishes.");
|
|
993
|
+
removeSchedule(harness);
|
|
994
|
+
for (const file of [configPath(harness), statePath(harness), pendingPath(harness)]) {
|
|
995
|
+
try { fs.unlinkSync(file); } catch (error) { if (error?.code !== "ENOENT") throw error; }
|
|
996
|
+
}
|
|
997
|
+
console.error(`${HARNESS_NAMES[harness]} is disconnected on this device. Revoke the old key in Shifu if you no longer want it usable.`);
|
|
998
|
+
}
|
|
999
|
+
|
|
888
1000
|
async function updateConfig(harness) {
|
|
889
1001
|
const config = readConfig(harness);
|
|
890
1002
|
const savedPolicy = normalizedPolicy(config);
|
|
@@ -1476,6 +1588,7 @@ async function main() {
|
|
|
1476
1588
|
const harness = requireHarness(option("--harness"));
|
|
1477
1589
|
if (command === "install") return install(harness);
|
|
1478
1590
|
if (command === "connect") return connect(harness);
|
|
1591
|
+
if (command === "disconnect") return disconnect(harness);
|
|
1479
1592
|
if (command === "config" && subCommand === "update") return updateConfig(harness);
|
|
1480
1593
|
if (command === "sync") return sync(harness);
|
|
1481
1594
|
if (command === "sync-files") return syncFilesNow(harness);
|
|
@@ -1485,7 +1598,7 @@ async function main() {
|
|
|
1485
1598
|
if (command === "status") return status(harness);
|
|
1486
1599
|
if (command === "sessions" || command === "discover" || (command === "session" && (subCommand === "discover" || subCommand === "list" || !subCommand || subCommand.startsWith("-")))) return sessions(harness);
|
|
1487
1600
|
if (command === "review") return review(harness);
|
|
1488
|
-
throw new Error("Use install, connect, config update, status, sessions, review, sync, sync-files, pending, approve, or scheduled-sync.");
|
|
1601
|
+
throw new Error("Use install, connect, disconnect, config update, status, sessions, review, sync, sync-files, pending, approve, or scheduled-sync.");
|
|
1489
1602
|
}
|
|
1490
1603
|
|
|
1491
1604
|
if (require.main === module) {
|
|
@@ -1497,4 +1610,4 @@ if (require.main === module) {
|
|
|
1497
1610
|
});
|
|
1498
1611
|
}
|
|
1499
1612
|
|
|
1500
|
-
module.exports = { approvalNotificationInvocation, claudeReview, claudeSession, codexReview, codexSession, configPath, configuredSyncPolicy, defaultInputFolder, finalAssistantNote, install, installDestination, localSessions, normalizedPolicy, notificationSessionRef, opencodeQuery, opencodeReview, opencodeSessions, payloadForSegment, pendingSegments, prepareInputFolder, readSecret, removeHooks, requireSafeApiUrl, reviewCandidates, schedulerArtifact, statePath, syncWorkFiles, textIsSafe, unsyncedCodexSessions, validateInputFolder, validSyncReceipt, validateApprovalMode, validateIntervalHours, validateSync, workFiles };
|
|
1613
|
+
module.exports = { approvalNotificationInvocation, claudeReview, claudeSession, codexReview, codexSession, configPath, configuredSyncPolicy, connect, defaultInputFolder, disconnect, finalAssistantNote, install, installDestination, localSessions, normalizedPolicy, notificationSessionRef, opencodeQuery, opencodeReview, opencodeSessions, payloadForSegment, pendingSegments, prepareInputFolder, readSecret, removeHooks, requireSafeApiUrl, reviewCandidates, schedulerArtifact, statePath, syncWorkFiles, textIsSafe, unsyncedCodexSessions, validateConnection, validateInputFolder, validSyncReceipt, validateApprovalMode, validateIntervalHours, validateSync, workFiles };
|
package/commands/shifu-sync.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Inspect or sync redacted OpenCode activity using the saved Shifu policy.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Run `npx @useshifu/coding-harness status --harness opencode` first.
|
|
5
|
+
Run `npx --yes @useshifu/coding-harness status --harness opencode` first.
|
|
6
6
|
|
|
7
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
|
|
|
@@ -12,8 +12,8 @@ For an explicit sync-now request:
|
|
|
12
12
|
|
|
13
13
|
1. Run `sessions --harness opencode --hours 24`, using 48 or 72 only when requested.
|
|
14
14
|
2. Review each returned segment with `review --harness opencode --session-ref <sessionRef> --hours <hours>`.
|
|
15
|
-
3. Prepare a redaction-version-four payload from final assistant notes only. Use
|
|
15
|
+
3. Prepare a redaction-version-four payload from final assistant notes only. Use `activity` evidence for writing, feedback, research, planning, product work, and any work that is not clearly an engineering implementation or decision. Activity evidence has no technical scope. An empty evidence list advances the checkpoint without creating a claim.
|
|
16
16
|
4. In automatic mode, pipe the payload to `sync --harness opencode` without asking again. In manual mode, show the exact payload, ask once, and then use `sync --approved --harness opencode`.
|
|
17
17
|
5. Keep the returned session reference and turn range unchanged. Never upload prompts, raw transcripts, tool output, code, paths, commands, URLs, credentials, names, customer details, or proprietary identifiers.
|
|
18
18
|
|
|
19
|
-
To connect or
|
|
19
|
+
To connect or replace a key, run `npx --yes @useshifu/coding-harness connect --harness opencode`. Connect asks for a key and verifies it with Shifu before reporting success. If this tool terminal cannot run the command or accept hidden input, ask the user to run it in their own interactive terminal and report the output; do not claim the connection succeeded. To disconnect locally, run `npx --yes @useshifu/coding-harness disconnect --harness opencode`. New connections use one-hour automatic sync and `~/Desktop/Shifu` on macOS by default. To change the interval, approval policy, or folder later, run `config update --harness opencode` with the desired flags (for example, `--input-folder <existing-readable-folder>`).
|
package/package.json
CHANGED
|
@@ -16,42 +16,44 @@ Only describe work attributable to the user. A team action item, meeting discuss
|
|
|
16
16
|
When the user asks to connect, run:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npx @useshifu/coding-harness connect --harness codex
|
|
19
|
+
npx --yes @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` up to 20 MB directly inside the folder; images and screenshots are not supported. Unknown formats are counted but never uploaded.
|
|
22
|
+
`connect` asks for a key every time and verifies it against Shifu for this harness before reporting success. If this tool terminal cannot run npx or accept hidden input, ask the user to run the command in their own interactive terminal and report its output; do not claim the connection succeeded. It 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. Replacing a key resets local checkpoints so work can sync to the new connection. It also excludes the setup session when the harness exposes its current opaque session reference. No content is sent during setup.
|
|
23
|
+
|
|
24
|
+
To disconnect locally, run `npx --yes @useshifu/coding-harness disconnect --harness codex`. Tell the user to revoke the old key in Shifu if they no longer want it usable.
|
|
23
25
|
|
|
24
26
|
When the user asks to change the interval, approval mode, or input folder, run:
|
|
25
27
|
|
|
26
28
|
```sh
|
|
27
|
-
npx @useshifu/coding-harness config update --harness codex --input-folder <existing-readable-folder>
|
|
29
|
+
npx --yes @useshifu/coding-harness config update --harness codex --input-folder <existing-readable-folder>
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
Use only the flags the user wants to change; existing settings are preserved. The CLI validates a custom folder before saving. Do not add a second confirmation around either command.
|
|
31
33
|
|
|
32
34
|
## Inspect status or queued work
|
|
33
35
|
|
|
34
|
-
Run `npx @useshifu/coding-harness status --harness codex` to inspect the saved policy, next scheduled run, pending count, and checkpoints.
|
|
36
|
+
Run `npx --yes @useshifu/coding-harness status --harness codex` to inspect the saved policy, next scheduled run, pending count, and checkpoints.
|
|
35
37
|
|
|
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.
|
|
38
|
+
For an explicit input-folder sync, run `npx --yes @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
39
|
|
|
38
40
|
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.
|
|
39
41
|
|
|
40
42
|
In manual mode, scheduled runs accumulate redacted segments locally. Run:
|
|
41
43
|
|
|
42
44
|
```sh
|
|
43
|
-
npx @useshifu/coding-harness pending --harness codex
|
|
45
|
+
npx --yes @useshifu/coding-harness pending --harness codex
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
Show the returned list and ask for one approval for the whole list. After approval, run `npx @useshifu/coding-harness approve --approved --harness codex`. Do not ask again.
|
|
48
|
+
Show the returned list and ask for one approval for the whole list. After approval, run `npx --yes @useshifu/coding-harness approve --approved --harness codex`. Do not ask again.
|
|
47
49
|
|
|
48
50
|
## Sync now
|
|
49
51
|
|
|
50
52
|
For an explicit sync request, first read `status` to determine the saved approval mode. Use the time window the user names; otherwise use 24 hours. Supported interactive windows are 24, 48, and 72 hours.
|
|
51
53
|
|
|
52
|
-
1. Run `npx @useshifu/coding-harness sessions --harness codex --hours <24|48|72>`.
|
|
54
|
+
1. Run `npx --yes @useshifu/coding-harness sessions --harness codex --hours <24|48|72>`.
|
|
53
55
|
2. Ignore `blockedSessions` unless the user specifically selects one. For a selected older backlog, add `--session-ref <sessionRef> --all`.
|
|
54
|
-
3. For each returned segment, run `npx @useshifu/coding-harness review --harness codex --session-ref <sessionRef> --hours <24|48|72>`.
|
|
56
|
+
3. For each returned segment, run `npx --yes @useshifu/coding-harness review --harness codex --session-ref <sessionRef> --hours <24|48|72>`.
|
|
55
57
|
4. Prepare a version-four payload from the returned final-assistant notes. Keep the returned `sessionRef`, `fromTurn`, and `toTurn` unchanged.
|
|
56
58
|
5. In automatic mode, send it without another approval. In manual mode, show the exact payload and ask once; after approval, pass `--approved` to the sync command.
|
|
57
59
|
6. Repeat discovery after accepted segments until the selected window is exhausted.
|
|
@@ -76,13 +78,13 @@ Use `kind: "activity"` for general harness work; it does not require a technical
|
|
|
76
78
|
Automatic mode:
|
|
77
79
|
|
|
78
80
|
```sh
|
|
79
|
-
printf '%s' '<redacted JSON>' | npx @useshifu/coding-harness sync --harness codex
|
|
81
|
+
printf '%s' '<redacted JSON>' | npx --yes @useshifu/coding-harness sync --harness codex
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
Manual mode, only after the user's single approval:
|
|
83
85
|
|
|
84
86
|
```sh
|
|
85
|
-
printf '%s' '<redacted JSON>' | npx @useshifu/coding-harness sync --approved --harness codex
|
|
87
|
+
printf '%s' '<redacted JSON>' | npx --yes @useshifu/coding-harness sync --approved --harness codex
|
|
86
88
|
```
|
|
87
89
|
|
|
88
90
|
If a transport or server error remains after the runner's retries, report that the checkpoint was not advanced. Do not alter the segment or skip ahead.
|