@useshifu/coding-harness 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/bin/shifu-harness.js +137 -53
- package/commands/shifu-sync.md +6 -5
- package/package.json +1 -1
- package/skills/shifu-sync/SKILL.md +8 -6
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ 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. Every `connect` asks for a connection key
|
|
23
|
+
No content is sent during setup. Every `connect` asks for a connection key using a normal terminal prompt, so the key is visible as you type or paste it. The key is verified 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 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
26
|
npx --yes @useshifu/coding-harness config update --harness <codex|claude_code|opencode> --interval-hours 1 --approval-mode automatic --input-folder ~/Desktop/Shifu
|
|
@@ -41,13 +41,19 @@ npx --yes @useshifu/coding-harness approve --harness codex
|
|
|
41
41
|
npx --yes @useshifu/coding-harness sync-files --harness codex
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
To select by session count instead of a time window, run `sessions --harness codex --count 5`. It selects the five most recently updated eligible sessions, newest first, without an hours cutoff. Current and setup sessions are excluded. `selectedSessionRefs` fixes that selection; `sessions` contains pending segments and `skippedSessions` reports already-synced sessions. Skipped sessions are not replaced with older ones. Review each pending session with `review --harness codex --session-ref <sessionRef> --all`. After an accepted segment, request the next with `sessions --harness codex --session-ref <sessionRef> --all` until that selected session is exhausted. Keep the original references instead of selecting another five.
|
|
45
|
+
|
|
46
|
+
Manual session sync runs immediately, independently of the hourly schedule. If a segment fails, report it as failed/pending; a checkpoint advances only after a matching server receipt. Report completed, already-synced, and failed session counts separately.
|
|
47
|
+
|
|
48
|
+
To upgrade an existing installation, run `npx --yes @useshifu/coding-harness@latest install --harness codex` (or `claude_code` / `opencode`). It replaces the local runner and harness instruction while preserving the connection, checkpoints, and schedule. Reconnecting is unnecessary. `status` includes `runnerVersion` so you can verify the installed version.
|
|
49
|
+
|
|
44
50
|
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
51
|
|
|
46
52
|
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.
|
|
47
53
|
|
|
48
54
|
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.
|
|
49
55
|
|
|
50
|
-
The connector captures supported harness work in engineering, product discovery, writing, interview feedback, research, and planning. Automatic sync turns completed actions
|
|
56
|
+
The connector captures supported harness work in engineering, product discovery, writing, interview feedback, research, and planning. Automatic sync turns completed actions, known work topics, descriptive feature or project types, and supported problem or approach details into separate controlled statements without sending source prose or raw names. If a segment has no supported work items, it advances the checkpoint without creating a claim. Version 1–3 clients remain supported.
|
|
51
57
|
|
|
52
58
|
## Sync a prepared payload
|
|
53
59
|
|
package/bin/shifu-harness.js
CHANGED
|
@@ -6,7 +6,6 @@ const os = require("node:os");
|
|
|
6
6
|
const path = require("node:path");
|
|
7
7
|
const { execFileSync } = require("node:child_process");
|
|
8
8
|
const readline = require("node:readline/promises");
|
|
9
|
-
const tty = require("node:tty");
|
|
10
9
|
|
|
11
10
|
const DEFAULT_API_URL = "https://api.useshifu.com";
|
|
12
11
|
const HARNESS_NAMES = {
|
|
@@ -38,6 +37,7 @@ const MAX_TURNS_PER_SEGMENT = 12;
|
|
|
38
37
|
const MAX_TITLE_LENGTH = 160;
|
|
39
38
|
const MAX_WORK_DETAIL_LENGTH = 600;
|
|
40
39
|
const CONFIG_VERSION = 2;
|
|
40
|
+
const RUNNER_VERSION = "0.3.8";
|
|
41
41
|
const REDACTION_VERSION = 4;
|
|
42
42
|
const DEFAULT_INTERVAL_HOURS = 1;
|
|
43
43
|
const LEGACY_INTERVAL_HOURS = 4;
|
|
@@ -588,23 +588,107 @@ const WORK_OBJECTS = [
|
|
|
588
588
|
{ key: "planning", title: "project plan", pattern: /\b(?:roadmap|project plan|delivery plan)\b/i },
|
|
589
589
|
];
|
|
590
590
|
|
|
591
|
+
// Only controlled feature descriptions leave the device during unattended sync.
|
|
592
|
+
const WORK_CONTEXTS = [
|
|
593
|
+
{ key: "payment_reconciliation", title: "payment reconciliation flow", phrase: "a payment reconciliation flow", pattern: /\bpayment reconciliation\b/i },
|
|
594
|
+
{ key: "payment", title: "payment flow", phrase: "a payment flow", pattern: /\b(?:payment|payments|payout|payouts)\b/i },
|
|
595
|
+
{ key: "checkout", title: "checkout flow", phrase: "a checkout flow", pattern: /\bcheckout\b/i },
|
|
596
|
+
{ key: "billing", title: "billing flow", phrase: "a billing flow", pattern: /\b(?:billing|invoic(?:e|es|ing)|subscription|subscriptions)\b/i },
|
|
597
|
+
{ key: "reconciliation", title: "reconciliation flow", phrase: "a reconciliation flow", pattern: /\breconciliation\b/i },
|
|
598
|
+
{ key: "onboarding", title: "onboarding flow", phrase: "an onboarding flow", pattern: /\bonboarding\b/i },
|
|
599
|
+
{ key: "search", title: "search experience", phrase: "a search experience", pattern: /\bsearch\b/i },
|
|
600
|
+
{ key: "notification", title: "notification flow", phrase: "a notification flow", pattern: /\bnotifications?\b/i },
|
|
601
|
+
{ key: "reporting", title: "reporting feature", phrase: "a reporting feature", pattern: /\b(?:reporting|reports)\b/i },
|
|
602
|
+
{ key: "messaging", title: "messaging feature", phrase: "a messaging feature", pattern: /\bmessaging\b/i },
|
|
603
|
+
{ key: "file_upload", title: "file upload flow", phrase: "a file upload flow", pattern: /\b(?:file uploads?|document uploads?)\b/i },
|
|
604
|
+
{ key: "scheduling", title: "scheduling flow", phrase: "a scheduling flow", pattern: /\b(?:scheduling|booking)\b/i },
|
|
605
|
+
{ key: "analytics", title: "analytics feature", phrase: "an analytics feature", pattern: /\banalytics\b/i },
|
|
606
|
+
{ key: "data_import", title: "data import flow", phrase: "a data import flow", pattern: /\b(?:data import|csv import|bulk import)\b/i },
|
|
607
|
+
{ key: "document_processing", title: "document processing flow", phrase: "a document processing flow", pattern: /\bdocument processing\b/i },
|
|
608
|
+
];
|
|
609
|
+
|
|
610
|
+
const WORK_DETAILS = [
|
|
611
|
+
{ title: "timeouts", pattern: /\btimeouts?\b/i },
|
|
612
|
+
{ title: "duplicates", pattern: /\bduplicat(?:e|es|ed|ion)\b/i },
|
|
613
|
+
{ title: "stale data", pattern: /\bstale (?:data|results?|cache)\b/i },
|
|
614
|
+
{ title: "race conditions", pattern: /\brace conditions?\b/i },
|
|
615
|
+
{ title: "data loss", pattern: /\bdata loss\b/i },
|
|
616
|
+
{ title: "latency", pattern: /\b(?:latency|slow responses?)\b/i },
|
|
617
|
+
{ title: "idempotency", pattern: /\bidempoten(?:t|cy)\b/i },
|
|
618
|
+
{ title: "backoff", pattern: /\bbackoff\b/i },
|
|
619
|
+
{ title: "validation", pattern: /\bvalidat(?:e|ed|ion|ing)\b/i },
|
|
620
|
+
{ title: "pagination", pattern: /\bpaginat(?:e|ed|ion|ing)\b/i },
|
|
621
|
+
{ title: "batching", pattern: /\bbatch(?:es|ed|ing)?\b/i },
|
|
622
|
+
{ title: "rate limiting", pattern: /\brate limit(?:s|ing|ed)?\b/i },
|
|
623
|
+
{ title: "deduplication", pattern: /\bdedupl?icat(?:e|ed|ion|ing)\b/i },
|
|
624
|
+
{ title: "fallback behavior", pattern: /\bfallback\b/i },
|
|
625
|
+
{ title: "accessibility", pattern: /\baccessibility\b/i },
|
|
626
|
+
{ title: "mobile layout", pattern: /\bmobile layout\b/i },
|
|
627
|
+
];
|
|
628
|
+
|
|
629
|
+
function controlledContexts(text) {
|
|
630
|
+
const matches = WORK_CONTEXTS.flatMap((item) => {
|
|
631
|
+
const match = item.pattern.exec(text);
|
|
632
|
+
if (!match) return [];
|
|
633
|
+
const type = text.slice(match.index + match[0].length).match(/^\s+(service|platform|system|feature|project|workflow|flow|experience)\b/i)?.[1];
|
|
634
|
+
if (!type) return [item];
|
|
635
|
+
const title = `${item.title.replace(/ (?:flow|feature|experience)$/, "")} ${type.toLowerCase()}`;
|
|
636
|
+
return [{ ...item, title, phrase: `${item.phrase.startsWith("an ") ? "an" : "a"} ${title}` }];
|
|
637
|
+
});
|
|
638
|
+
const contexts = matches.some((item) => item.key === "payment_reconciliation") ? matches.filter((item) => item.key !== "payment" && item.key !== "reconciliation") : matches;
|
|
639
|
+
return contexts.length <= 2 ? contexts : null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function controlledDetails(text) {
|
|
643
|
+
return WORK_DETAILS.map((item) => ({ item, index: item.pattern.exec(text)?.index }))
|
|
644
|
+
.filter(({ index }) => index !== undefined)
|
|
645
|
+
.sort((left, right) => left.index - right.index)
|
|
646
|
+
.slice(0, 3)
|
|
647
|
+
.map(({ item }) => item.title);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function withoutNamedLabels(text) {
|
|
651
|
+
return text.replace(/\b(?:Project|Client|Customer|Company)\s+[A-Z][A-Za-z0-9_-]*/g, "");
|
|
652
|
+
}
|
|
653
|
+
|
|
591
654
|
function safeActivityEvidence(notes) {
|
|
592
655
|
const evidence = [];
|
|
593
656
|
const seen = new Set();
|
|
594
657
|
for (const [noteIndex, { turn, note }] of notes.entries()) {
|
|
595
658
|
let inCodeBlock = false;
|
|
659
|
+
let sectionContexts = [];
|
|
596
660
|
for (const [lineIndex, rawLine] of note.split("\n").entries()) {
|
|
597
661
|
if (rawLine.trim().startsWith("```")) { inCodeBlock = !inCodeBlock; continue; }
|
|
598
662
|
if (inCodeBlock) continue;
|
|
663
|
+
const heading = rawLine.trim().match(/^(?:#{1,6}\s+(.+)|\*\*(.+)\*\*:?\s*)$/);
|
|
664
|
+
if (heading) {
|
|
665
|
+
const label = withoutNamedLabels(heading[1] || heading[2]);
|
|
666
|
+
sectionContexts = /\b(?:project|feature|flow|service|system|product)\b/i.test(label) ? controlledContexts(label) || [] : [];
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
599
669
|
const line = rawLine.trim().replace(/^[-*]\s+/, "").split(/[.;—]/, 1)[0];
|
|
670
|
+
const clauses = line.split(/\s+(?:and|but|while)\s+/i);
|
|
671
|
+
const nextAction = clauses.findIndex((clause, index) => index > 0 && WORK_ACTIONS.some((item) => item.pattern.test(clause)));
|
|
672
|
+
const workClause = withoutNamedLabels(nextAction > 0 ? clauses.slice(0, nextAction).join(" and ") : line);
|
|
600
673
|
const action = WORK_ACTIONS.find((item) => item.pattern.test(line));
|
|
601
|
-
const
|
|
674
|
+
const detailSource = workClause.split(/\b(?:about|titled)\b/i, 1)[0];
|
|
675
|
+
const contexts = controlledContexts(detailSource);
|
|
676
|
+
const featureContexts = contexts === null ? [] : contexts.length ? contexts : sectionContexts;
|
|
677
|
+
let object;
|
|
678
|
+
let objectPosition = Infinity;
|
|
679
|
+
for (const item of WORK_OBJECTS) {
|
|
680
|
+
const match = item.pattern.exec(workClause);
|
|
681
|
+
if (match && match.index < objectPosition) { object = item; objectPosition = match.index; }
|
|
682
|
+
}
|
|
683
|
+
object ||= featureContexts[0];
|
|
602
684
|
if (!action || !object) continue;
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
685
|
+
const context = ["interview_feedback", "professional_post"].includes(object.key) ? [] : featureContexts.filter((item) => item !== object);
|
|
686
|
+
const details = controlledDetails(detailSource).filter((detail) => !object.title.includes(detail));
|
|
687
|
+
const title = `${action.title} ${object.title}${context.length ? ` for ${context.map((item) => item.phrase).join(" and ")}` : ""}`;
|
|
688
|
+
const statement = `${title}${details.length ? ` (${details.join(", ")})` : ""}.`;
|
|
689
|
+
if (seen.has(statement)) continue;
|
|
690
|
+
seen.add(statement);
|
|
691
|
+
evidence.push({ kind: "activity", title, statement, itemRef: `t${turn}_n${noteIndex + 1}_l${lineIndex + 1}` });
|
|
608
692
|
if (evidence.length === MAX_EVIDENCE_ITEMS) return evidence;
|
|
609
693
|
}
|
|
610
694
|
}
|
|
@@ -615,8 +699,8 @@ function payloadForSegment(harness, segment) {
|
|
|
615
699
|
const { notes } = reviewSegment(harness, segment.sessionRef, segment.fromTurn, segment.toTurn);
|
|
616
700
|
const evidence = safeActivityEvidence(notes);
|
|
617
701
|
const title = evidence.length ? evidence[0].title : "No supported work identified";
|
|
618
|
-
const summary = evidence.length
|
|
619
|
-
|
|
702
|
+
const summary = evidence.length ? `${evidence.slice(0, 3).map((item) => item.statement).join(" ")}${evidence.length > 3 ? ` ${evidence.length - 3} more supported work items.` : ""}` :
|
|
703
|
+
"This range had no supported work items.";
|
|
620
704
|
return {
|
|
621
705
|
harness,
|
|
622
706
|
sessionRef: segment.sessionRef,
|
|
@@ -754,8 +838,9 @@ function validateGeneratedProfile(profile, harness) {
|
|
|
754
838
|
}
|
|
755
839
|
}
|
|
756
840
|
|
|
757
|
-
function
|
|
758
|
-
if (![24, 48, 72].includes(hours)) throw new Error("Choose --hours 24, 48, or 72.");
|
|
841
|
+
function selectedSessionSegments(harness, { hours = 24, turns = MAX_TURNS_PER_SEGMENT, selectedRef, includeOlder = false, count } = {}) {
|
|
842
|
+
if (count === undefined && ![24, 48, 72].includes(hours)) throw new Error("Choose --hours 24, 48, or 72.");
|
|
843
|
+
if (count !== undefined && (!Number.isSafeInteger(count) || count < 1)) throw new Error("Choose a positive whole number for --count.");
|
|
759
844
|
if (!Number.isInteger(turns) || turns < 1 || turns > MAX_TURNS_PER_SEGMENT) throw new Error("Choose --turns between 1 and 12.");
|
|
760
845
|
const since = Date.now() - hours * 3_600_000;
|
|
761
846
|
const state = readState(harness);
|
|
@@ -765,7 +850,7 @@ function unsyncedCodexSessions(harness, hours, turns = MAX_TURNS_PER_SEGMENT, se
|
|
|
765
850
|
const sessions = new Map();
|
|
766
851
|
for (const session of localSessions(harness)
|
|
767
852
|
.filter((session) => !excluded.has(session.sessionRef))
|
|
768
|
-
.filter((session) => selectedRef ? session.sessionRef === selectedRef : Date.parse(session.updatedAt) >= since)) {
|
|
853
|
+
.filter((session) => selectedRef ? session.sessionRef === selectedRef : count !== undefined || Date.parse(session.updatedAt) >= since)) {
|
|
769
854
|
const existing = sessions.get(session.sessionRef);
|
|
770
855
|
sessions.set(session.sessionRef, existing ? {
|
|
771
856
|
...session,
|
|
@@ -776,7 +861,10 @@ function unsyncedCodexSessions(harness, hours, turns = MAX_TURNS_PER_SEGMENT, se
|
|
|
776
861
|
} : session);
|
|
777
862
|
}
|
|
778
863
|
return [...sessions.values()]
|
|
779
|
-
.sort((left, right) =>
|
|
864
|
+
.sort((left, right) => count === undefined
|
|
865
|
+
? left.updatedAt.localeCompare(right.updatedAt)
|
|
866
|
+
: right.updatedAt.localeCompare(left.updatedAt) || left.sessionRef.localeCompare(right.sessionRef))
|
|
867
|
+
.slice(0, count ?? Infinity)
|
|
780
868
|
.map(({ turnTimes, ...session }) => {
|
|
781
869
|
const checkpoint = state.sessions[session.sessionRef]?.lastSyncedTurn || 0;
|
|
782
870
|
return {
|
|
@@ -787,7 +875,11 @@ function unsyncedCodexSessions(harness, hours, turns = MAX_TURNS_PER_SEGMENT, se
|
|
|
787
875
|
oldestUnsyncedAt: turnTimes[checkpoint] || null,
|
|
788
876
|
};
|
|
789
877
|
})
|
|
790
|
-
.filter((session) =>
|
|
878
|
+
.filter((session) => count !== undefined || includeOlder || Date.parse(session.oldestUnsyncedAt) >= since);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function unsyncedCodexSessions(harness, options = {}) {
|
|
882
|
+
return selectedSessionSegments(harness, options).filter((session) => session.fromTurn <= session.toTurn);
|
|
791
883
|
}
|
|
792
884
|
|
|
793
885
|
function pendingSegments(harness) {
|
|
@@ -833,41 +925,17 @@ async function confirm(question) {
|
|
|
833
925
|
}
|
|
834
926
|
}
|
|
835
927
|
|
|
836
|
-
async function readSecret(input = process.stdin) {
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
928
|
+
async function readSecret(input = process.stdin, output = process.stderr) {
|
|
929
|
+
const terminal = readline.createInterface({ input, output });
|
|
930
|
+
try {
|
|
931
|
+
const answer = terminal.question("Connection key: ");
|
|
932
|
+
const closed = new Promise((_, reject) => terminal.once("close", () => reject(new Error("Connection key input closed."))));
|
|
933
|
+
return await Promise.race([answer, closed]);
|
|
934
|
+
} catch {
|
|
935
|
+
throw new Error("Could not read a connection key. Run connect in an interactive terminal.");
|
|
936
|
+
} finally {
|
|
937
|
+
terminal.close();
|
|
843
938
|
}
|
|
844
|
-
const wasRaw = input.isRaw;
|
|
845
|
-
process.stderr.write("Connection key (hidden): ");
|
|
846
|
-
input.setRawMode(true);
|
|
847
|
-
input.setEncoding("utf8");
|
|
848
|
-
return new Promise((resolve, reject) => {
|
|
849
|
-
let value = "";
|
|
850
|
-
const finish = (error) => {
|
|
851
|
-
input.off("data", onData);
|
|
852
|
-
input.off("error", onError);
|
|
853
|
-
input.setRawMode(Boolean(wasRaw));
|
|
854
|
-
if (ownsInput) input.destroy();
|
|
855
|
-
process.stderr.write("\n");
|
|
856
|
-
if (error) reject(error);
|
|
857
|
-
else resolve(value);
|
|
858
|
-
};
|
|
859
|
-
const onError = (error) => finish(error);
|
|
860
|
-
const onData = (chunk) => {
|
|
861
|
-
for (const character of chunk) {
|
|
862
|
-
if (character === "\r" || character === "\n") return finish();
|
|
863
|
-
if (character === "\u0003") return finish(new Error("Connection was cancelled."));
|
|
864
|
-
if (character === "\u007f" || character === "\b") value = value.slice(0, -1);
|
|
865
|
-
else if (character >= " " && character !== "\u007f") value += character;
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
input.on("data", onData);
|
|
869
|
-
input.on("error", onError);
|
|
870
|
-
});
|
|
871
939
|
}
|
|
872
940
|
|
|
873
941
|
function copyRunner() {
|
|
@@ -953,7 +1021,9 @@ function install(harness, quiet = false) {
|
|
|
953
1021
|
if (!quiet) {
|
|
954
1022
|
console.error(`Installed the Shifu sync instruction for ${HARNESS_NAMES[harness]}.`);
|
|
955
1023
|
if (removedHooks) console.error("Removed obsolete Codex lifecycle hooks.");
|
|
956
|
-
console.error(
|
|
1024
|
+
console.error(fs.existsSync(configPath(harness))
|
|
1025
|
+
? "Updated the local runner and sync instruction. Your connection, checkpoints, and schedule were preserved."
|
|
1026
|
+
: `Next: npx --yes @useshifu/coding-harness connect --harness ${harness}`);
|
|
957
1027
|
}
|
|
958
1028
|
}
|
|
959
1029
|
|
|
@@ -1696,7 +1766,7 @@ function status(harness) {
|
|
|
1696
1766
|
if (folder) {
|
|
1697
1767
|
try { unsupportedFiles = scanWorkFiles(folder).unsupported; } catch (error) { inputFolderError = error.message; }
|
|
1698
1768
|
}
|
|
1699
|
-
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));
|
|
1769
|
+
console.log(JSON.stringify({ harness, runnerVersion: RUNNER_VERSION, 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));
|
|
1700
1770
|
}
|
|
1701
1771
|
|
|
1702
1772
|
async function syncFilesNow(harness) {
|
|
@@ -1708,16 +1778,30 @@ async function syncFilesNow(harness) {
|
|
|
1708
1778
|
|
|
1709
1779
|
function sessions(harness) {
|
|
1710
1780
|
readConfig(harness);
|
|
1781
|
+
const count = option("--count");
|
|
1782
|
+
if (hasFlag("--count") && count === undefined) throw new Error("Provide a value for --count.");
|
|
1783
|
+
if (count !== undefined && option("--hours") !== undefined) throw new Error("Use either --count or --hours, not both.");
|
|
1711
1784
|
const hours = numberOption("--hours", 24);
|
|
1712
1785
|
const turns = numberOption("--turns", MAX_TURNS_PER_SEGMENT);
|
|
1713
1786
|
const sessionRef = option("--session-ref");
|
|
1714
1787
|
const all = hasFlag("--all");
|
|
1788
|
+
if (count !== undefined && (sessionRef || all)) throw new Error("Use --count without --session-ref or --all.");
|
|
1715
1789
|
if (all && !sessionRef) throw new Error("Use --all only with an explicitly selected --session-ref.");
|
|
1716
|
-
const
|
|
1717
|
-
const
|
|
1790
|
+
const countValue = count === undefined ? undefined : Number(count);
|
|
1791
|
+
const selected = selectedSessionSegments(harness, { hours, turns, selectedRef: sessionRef, includeOlder: all, count: countValue });
|
|
1792
|
+
const pending = selected.filter((session) => session.fromTurn <= session.toTurn);
|
|
1793
|
+
const blocked = all || count !== undefined ? [] : unsyncedCodexSessions(harness, { hours, turns, selectedRef: sessionRef, includeOlder: true })
|
|
1718
1794
|
.filter((session) => !pending.some((item) => item.sessionRef === session.sessionRef))
|
|
1719
1795
|
.map(({ sessionRef, oldestUnsyncedAt }) => ({ sessionRef, oldestUnsyncedAt }));
|
|
1720
|
-
console.log(JSON.stringify(
|
|
1796
|
+
console.log(JSON.stringify(count === undefined
|
|
1797
|
+
? { harness, hours, turns, sessions: pending, blockedSessions: blocked }
|
|
1798
|
+
: {
|
|
1799
|
+
harness, count: countValue, order: "latest_to_earliest", turns,
|
|
1800
|
+
selectedSessionRefs: selected.map((session) => session.sessionRef),
|
|
1801
|
+
sessions: pending,
|
|
1802
|
+
skippedSessions: selected.filter((session) => session.fromTurn > session.toTurn)
|
|
1803
|
+
.map(({ sessionRef }) => ({ sessionRef, reason: "already_synced" })),
|
|
1804
|
+
}, null, 2));
|
|
1721
1805
|
}
|
|
1722
1806
|
|
|
1723
1807
|
function review(harness) {
|
package/commands/shifu-sync.md
CHANGED
|
@@ -10,10 +10,11 @@ If manual approval has queued scheduled work, run `pending --harness opencode`,
|
|
|
10
10
|
|
|
11
11
|
For an explicit sync-now request:
|
|
12
12
|
|
|
13
|
-
1.
|
|
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 `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.
|
|
13
|
+
1. For a time-window request, run `sessions --harness opencode --hours 24`, using 48 or 72 only when requested. For a request by number of recent sessions, run `sessions --harness opencode --count <count>`; this ignores the hours window and returns the most recently updated sessions first.
|
|
14
|
+
2. Review each returned segment with `review --harness opencode --session-ref <sessionRef> --hours <hours>`. For count-based selection, use `review --harness opencode --session-ref <sessionRef> --all` so older sessions are allowed.
|
|
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. Preserve the supported action, feature or project purpose, specific problem, approach, and verified result. Synthesize a descriptive project label (such as "payment reconciliation service") from its purpose; never copy a raw label that could identify a company or person. Keep distinct contributions separate, including different fixes within one feature. 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
|
-
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.
|
|
17
|
+
5. Keep the returned session reference and turn range unchanged. Never upload prompts, raw transcripts, tool output, code, paths, commands, URLs, credentials, company or person names, customer details, or proprietary identifiers.
|
|
18
|
+
6. For a count-based sync, keep `selectedSessionRefs` from the first discovery and review only pending `sessions`. `skippedSessions` reports already-synced selections; do not replace them with older sessions. If one has remaining turns, request its next segment with `sessions --harness opencode --session-ref <sessionRef> --all`. Stop after those selected sessions are exhausted. Report completed, already-synced, and failed/pending sessions separately. Manual sync runs immediately, independently of the hourly schedule.
|
|
18
19
|
|
|
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
|
|
20
|
+
To connect or replace a key, run `npx --yes @useshifu/coding-harness connect --harness opencode`. Connect asks for a key with a normal visible terminal prompt and verifies it with Shifu before reporting success. Do not ask the user to paste the key into chat. If this tool terminal cannot run the command or accept 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
|
@@ -7,7 +7,7 @@ description: Connect, configure, inspect, or sync coding-harness activity, AI Pr
|
|
|
7
7
|
|
|
8
8
|
Shifu records useful work completed through the harness. Work may be engineering, product discovery, writing, interview feedback, research, planning, or another user-directed activity. Do not force non-engineering work into engineering categories.
|
|
9
9
|
|
|
10
|
-
Never include user prompts, raw transcripts, tool output, source code, commands, credentials, URLs, file paths, names, customer details, or proprietary identifiers in a coding-session payload. The local coding runner reads final assistant notes for career activity and scans user turns locally for fixed AI Profile signals; it sends neither raw source. Files placed in the user's chosen Shifu input folder are a separate path: the runner sends supported files over TLS for server-side parsing and sanitization. Do not describe folder files as locally redacted.
|
|
10
|
+
Never include user prompts, raw transcripts, tool output, source code, commands, credentials, URLs, file paths, company or person names, customer details, or proprietary identifiers in a coding-session payload. Synthesize descriptive project labels from their purpose rather than copying a raw name. The local coding runner reads final assistant notes for career activity and scans user turns locally for fixed AI Profile signals; it sends neither raw source. Files placed in the user's chosen Shifu input folder are a separate path: the runner sends supported files over TLS for server-side parsing and sanitization. Do not describe folder files as locally redacted.
|
|
11
11
|
|
|
12
12
|
Only describe work attributable to the user. A team action item, meeting discussion, or another person's contribution is not evidence that the user did it. If a final note does not support a specific user contribution, do not invent one.
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ When the user asks to connect, run:
|
|
|
23
23
|
npx --yes @useshifu/coding-harness connect --harness codex
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
`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
|
|
26
|
+
`connect` asks for a key with a normal visible terminal prompt every time and verifies it against Shifu for this harness before reporting success. Do not ask the user to paste the key into chat. If this tool terminal cannot run npx or accept 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.
|
|
27
27
|
|
|
28
28
|
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.
|
|
29
29
|
|
|
@@ -53,17 +53,19 @@ Show the returned list and ask for one approval for the whole list. After approv
|
|
|
53
53
|
|
|
54
54
|
## Sync now
|
|
55
55
|
|
|
56
|
-
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.
|
|
56
|
+
For an explicit sync request, first read `status` to determine the saved approval mode. Manual sync runs immediately and does not wait for the hourly schedule. Use the time window the user names; otherwise use 24 hours. Supported interactive windows are 24, 48, and 72 hours. If the user specifies a number of recent sessions, use count-based selection instead; it has no hours cutoff and orders sessions from latest to earliest. It selects the latest eligible sessions, excluding current/setup sessions, and reports already-synced selections in `skippedSessions` without replacing them with older sessions.
|
|
57
57
|
|
|
58
|
-
1.
|
|
58
|
+
1. For a time-window sync, run `npx --yes @useshifu/coding-harness sessions --harness codex --hours <24|48|72>`. For a count-based sync, run `npx --yes @useshifu/coding-harness sessions --harness codex --count <count>`; do not add `--hours`.
|
|
59
59
|
2. Ignore `blockedSessions` unless the user specifically selects one. For a selected older backlog, add `--session-ref <sessionRef> --all`.
|
|
60
|
-
3. For each returned segment, run `npx --yes @useshifu/coding-harness review --harness codex --session-ref <sessionRef> --hours <24|48|72>`.
|
|
60
|
+
3. For each returned segment, run `npx --yes @useshifu/coding-harness review --harness codex --session-ref <sessionRef> --hours <24|48|72>`. For count-based selection, use `--all` instead of `--hours` so the review has no time cutoff.
|
|
61
61
|
4. Prepare a version-four payload from the returned final-assistant notes. Keep the returned `sessionRef`, `fromTurn`, and `toTurn` unchanged.
|
|
62
62
|
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.
|
|
63
|
-
6.
|
|
63
|
+
6. For a time-window sync, repeat discovery after accepted segments until the selected window is exhausted. For a count-based sync, keep `selectedSessionRefs` from the first discovery and review only pending `sessions`. If a selected session has remaining turns, request its next segment with `sessions --harness codex --session-ref <sessionRef> --all`; stop after those selected sessions are exhausted. Report completed, already-synced, and failed/pending sessions separately. A failed session does not count as completed.
|
|
64
64
|
|
|
65
65
|
Use `kind: "activity"` for general harness work; it does not require a technical scope. Use `implementation` or `decision` only when the note clearly supports that engineering classification, and then use the smallest reviewed scope among `unit`, `module`, `service`, `system`, and `product`. Evidence may be empty when the safe final note supports only a session-level title and summary. Never invent impact, ownership, verification, or outcomes.
|
|
66
66
|
|
|
67
|
+
Make each supported item useful for later recall: retain the user's action, the feature or project purpose, the specific problem, the approach, and a verified result when the final note supports each part. A descriptive project label such as "payment reconciliation service" is useful; synthesize it from the work's purpose instead of copying a raw project label that might contain a company or person's name. Keep distinct contributions separate, including two fixes in one feature that addressed different problems. Omit names of companies and people, customer details, proprietary identifiers, paths, code snippets, and unsupported impact. If a detail cannot be safely described, keep the supported parts. Prefer a summary that names the main distinct work items over a generic count alone.
|
|
68
|
+
|
|
67
69
|
```json
|
|
68
70
|
{
|
|
69
71
|
"harness": "codex",
|