@useshifu/coding-harness 0.3.6 → 0.3.7
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 +102 -43
- package/commands/shifu-sync.md +3 -3
- package/package.json +1 -1
- package/skills/shifu-sync/SKILL.md +4 -2
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
|
|
@@ -47,7 +47,7 @@ The same scheduled run checks the input folder. `sync-files` checks it immediate
|
|
|
47
47
|
|
|
48
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.
|
|
49
49
|
|
|
50
|
-
The connector captures supported harness work in engineering, product discovery, writing, interview feedback, research, and planning. Automatic sync turns completed actions
|
|
50
|
+
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
51
|
|
|
52
52
|
## Sync a prepared payload
|
|
53
53
|
|
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 = {
|
|
@@ -588,23 +587,107 @@ const WORK_OBJECTS = [
|
|
|
588
587
|
{ key: "planning", title: "project plan", pattern: /\b(?:roadmap|project plan|delivery plan)\b/i },
|
|
589
588
|
];
|
|
590
589
|
|
|
590
|
+
// Only controlled feature descriptions leave the device during unattended sync.
|
|
591
|
+
const WORK_CONTEXTS = [
|
|
592
|
+
{ key: "payment_reconciliation", title: "payment reconciliation flow", phrase: "a payment reconciliation flow", pattern: /\bpayment reconciliation\b/i },
|
|
593
|
+
{ key: "payment", title: "payment flow", phrase: "a payment flow", pattern: /\b(?:payment|payments|payout|payouts)\b/i },
|
|
594
|
+
{ key: "checkout", title: "checkout flow", phrase: "a checkout flow", pattern: /\bcheckout\b/i },
|
|
595
|
+
{ key: "billing", title: "billing flow", phrase: "a billing flow", pattern: /\b(?:billing|invoic(?:e|es|ing)|subscription|subscriptions)\b/i },
|
|
596
|
+
{ key: "reconciliation", title: "reconciliation flow", phrase: "a reconciliation flow", pattern: /\breconciliation\b/i },
|
|
597
|
+
{ key: "onboarding", title: "onboarding flow", phrase: "an onboarding flow", pattern: /\bonboarding\b/i },
|
|
598
|
+
{ key: "search", title: "search experience", phrase: "a search experience", pattern: /\bsearch\b/i },
|
|
599
|
+
{ key: "notification", title: "notification flow", phrase: "a notification flow", pattern: /\bnotifications?\b/i },
|
|
600
|
+
{ key: "reporting", title: "reporting feature", phrase: "a reporting feature", pattern: /\b(?:reporting|reports)\b/i },
|
|
601
|
+
{ key: "messaging", title: "messaging feature", phrase: "a messaging feature", pattern: /\bmessaging\b/i },
|
|
602
|
+
{ key: "file_upload", title: "file upload flow", phrase: "a file upload flow", pattern: /\b(?:file uploads?|document uploads?)\b/i },
|
|
603
|
+
{ key: "scheduling", title: "scheduling flow", phrase: "a scheduling flow", pattern: /\b(?:scheduling|booking)\b/i },
|
|
604
|
+
{ key: "analytics", title: "analytics feature", phrase: "an analytics feature", pattern: /\banalytics\b/i },
|
|
605
|
+
{ key: "data_import", title: "data import flow", phrase: "a data import flow", pattern: /\b(?:data import|csv import|bulk import)\b/i },
|
|
606
|
+
{ key: "document_processing", title: "document processing flow", phrase: "a document processing flow", pattern: /\bdocument processing\b/i },
|
|
607
|
+
];
|
|
608
|
+
|
|
609
|
+
const WORK_DETAILS = [
|
|
610
|
+
{ title: "timeouts", pattern: /\btimeouts?\b/i },
|
|
611
|
+
{ title: "duplicates", pattern: /\bduplicat(?:e|es|ed|ion)\b/i },
|
|
612
|
+
{ title: "stale data", pattern: /\bstale (?:data|results?|cache)\b/i },
|
|
613
|
+
{ title: "race conditions", pattern: /\brace conditions?\b/i },
|
|
614
|
+
{ title: "data loss", pattern: /\bdata loss\b/i },
|
|
615
|
+
{ title: "latency", pattern: /\b(?:latency|slow responses?)\b/i },
|
|
616
|
+
{ title: "idempotency", pattern: /\bidempoten(?:t|cy)\b/i },
|
|
617
|
+
{ title: "backoff", pattern: /\bbackoff\b/i },
|
|
618
|
+
{ title: "validation", pattern: /\bvalidat(?:e|ed|ion|ing)\b/i },
|
|
619
|
+
{ title: "pagination", pattern: /\bpaginat(?:e|ed|ion|ing)\b/i },
|
|
620
|
+
{ title: "batching", pattern: /\bbatch(?:es|ed|ing)?\b/i },
|
|
621
|
+
{ title: "rate limiting", pattern: /\brate limit(?:s|ing|ed)?\b/i },
|
|
622
|
+
{ title: "deduplication", pattern: /\bdedupl?icat(?:e|ed|ion|ing)\b/i },
|
|
623
|
+
{ title: "fallback behavior", pattern: /\bfallback\b/i },
|
|
624
|
+
{ title: "accessibility", pattern: /\baccessibility\b/i },
|
|
625
|
+
{ title: "mobile layout", pattern: /\bmobile layout\b/i },
|
|
626
|
+
];
|
|
627
|
+
|
|
628
|
+
function controlledContexts(text) {
|
|
629
|
+
const matches = WORK_CONTEXTS.flatMap((item) => {
|
|
630
|
+
const match = item.pattern.exec(text);
|
|
631
|
+
if (!match) return [];
|
|
632
|
+
const type = text.slice(match.index + match[0].length).match(/^\s+(service|platform|system|feature|project|workflow|flow|experience)\b/i)?.[1];
|
|
633
|
+
if (!type) return [item];
|
|
634
|
+
const title = `${item.title.replace(/ (?:flow|feature|experience)$/, "")} ${type.toLowerCase()}`;
|
|
635
|
+
return [{ ...item, title, phrase: `${item.phrase.startsWith("an ") ? "an" : "a"} ${title}` }];
|
|
636
|
+
});
|
|
637
|
+
const contexts = matches.some((item) => item.key === "payment_reconciliation") ? matches.filter((item) => item.key !== "payment" && item.key !== "reconciliation") : matches;
|
|
638
|
+
return contexts.length <= 2 ? contexts : null;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function controlledDetails(text) {
|
|
642
|
+
return WORK_DETAILS.map((item) => ({ item, index: item.pattern.exec(text)?.index }))
|
|
643
|
+
.filter(({ index }) => index !== undefined)
|
|
644
|
+
.sort((left, right) => left.index - right.index)
|
|
645
|
+
.slice(0, 3)
|
|
646
|
+
.map(({ item }) => item.title);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function withoutNamedLabels(text) {
|
|
650
|
+
return text.replace(/\b(?:Project|Client|Customer|Company)\s+[A-Z][A-Za-z0-9_-]*/g, "");
|
|
651
|
+
}
|
|
652
|
+
|
|
591
653
|
function safeActivityEvidence(notes) {
|
|
592
654
|
const evidence = [];
|
|
593
655
|
const seen = new Set();
|
|
594
656
|
for (const [noteIndex, { turn, note }] of notes.entries()) {
|
|
595
657
|
let inCodeBlock = false;
|
|
658
|
+
let sectionContexts = [];
|
|
596
659
|
for (const [lineIndex, rawLine] of note.split("\n").entries()) {
|
|
597
660
|
if (rawLine.trim().startsWith("```")) { inCodeBlock = !inCodeBlock; continue; }
|
|
598
661
|
if (inCodeBlock) continue;
|
|
662
|
+
const heading = rawLine.trim().match(/^(?:#{1,6}\s+(.+)|\*\*(.+)\*\*:?\s*)$/);
|
|
663
|
+
if (heading) {
|
|
664
|
+
const label = withoutNamedLabels(heading[1] || heading[2]);
|
|
665
|
+
sectionContexts = /\b(?:project|feature|flow|service|system|product)\b/i.test(label) ? controlledContexts(label) || [] : [];
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
599
668
|
const line = rawLine.trim().replace(/^[-*]\s+/, "").split(/[.;—]/, 1)[0];
|
|
669
|
+
const clauses = line.split(/\s+(?:and|but|while)\s+/i);
|
|
670
|
+
const nextAction = clauses.findIndex((clause, index) => index > 0 && WORK_ACTIONS.some((item) => item.pattern.test(clause)));
|
|
671
|
+
const workClause = withoutNamedLabels(nextAction > 0 ? clauses.slice(0, nextAction).join(" and ") : line);
|
|
600
672
|
const action = WORK_ACTIONS.find((item) => item.pattern.test(line));
|
|
601
|
-
const
|
|
673
|
+
const detailSource = workClause.split(/\b(?:about|titled)\b/i, 1)[0];
|
|
674
|
+
const contexts = controlledContexts(detailSource);
|
|
675
|
+
const featureContexts = contexts === null ? [] : contexts.length ? contexts : sectionContexts;
|
|
676
|
+
let object;
|
|
677
|
+
let objectPosition = Infinity;
|
|
678
|
+
for (const item of WORK_OBJECTS) {
|
|
679
|
+
const match = item.pattern.exec(workClause);
|
|
680
|
+
if (match && match.index < objectPosition) { object = item; objectPosition = match.index; }
|
|
681
|
+
}
|
|
682
|
+
object ||= featureContexts[0];
|
|
602
683
|
if (!action || !object) continue;
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
684
|
+
const context = ["interview_feedback", "professional_post"].includes(object.key) ? [] : featureContexts.filter((item) => item !== object);
|
|
685
|
+
const details = controlledDetails(detailSource).filter((detail) => !object.title.includes(detail));
|
|
686
|
+
const title = `${action.title} ${object.title}${context.length ? ` for ${context.map((item) => item.phrase).join(" and ")}` : ""}`;
|
|
687
|
+
const statement = `${title}${details.length ? ` (${details.join(", ")})` : ""}.`;
|
|
688
|
+
if (seen.has(statement)) continue;
|
|
689
|
+
seen.add(statement);
|
|
690
|
+
evidence.push({ kind: "activity", title, statement, itemRef: `t${turn}_n${noteIndex + 1}_l${lineIndex + 1}` });
|
|
608
691
|
if (evidence.length === MAX_EVIDENCE_ITEMS) return evidence;
|
|
609
692
|
}
|
|
610
693
|
}
|
|
@@ -615,8 +698,8 @@ function payloadForSegment(harness, segment) {
|
|
|
615
698
|
const { notes } = reviewSegment(harness, segment.sessionRef, segment.fromTurn, segment.toTurn);
|
|
616
699
|
const evidence = safeActivityEvidence(notes);
|
|
617
700
|
const title = evidence.length ? evidence[0].title : "No supported work identified";
|
|
618
|
-
const summary = evidence.length
|
|
619
|
-
|
|
701
|
+
const summary = evidence.length ? `${evidence.slice(0, 3).map((item) => item.statement).join(" ")}${evidence.length > 3 ? ` ${evidence.length - 3} more supported work items.` : ""}` :
|
|
702
|
+
"This range had no supported work items.";
|
|
620
703
|
return {
|
|
621
704
|
harness,
|
|
622
705
|
sessionRef: segment.sessionRef,
|
|
@@ -833,41 +916,17 @@ async function confirm(question) {
|
|
|
833
916
|
}
|
|
834
917
|
}
|
|
835
918
|
|
|
836
|
-
async function readSecret(input = process.stdin) {
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
919
|
+
async function readSecret(input = process.stdin, output = process.stderr) {
|
|
920
|
+
const terminal = readline.createInterface({ input, output });
|
|
921
|
+
try {
|
|
922
|
+
const answer = terminal.question("Connection key: ");
|
|
923
|
+
const closed = new Promise((_, reject) => terminal.once("close", () => reject(new Error("Connection key input closed."))));
|
|
924
|
+
return await Promise.race([answer, closed]);
|
|
925
|
+
} catch {
|
|
926
|
+
throw new Error("Could not read a connection key. Run connect in an interactive terminal.");
|
|
927
|
+
} finally {
|
|
928
|
+
terminal.close();
|
|
843
929
|
}
|
|
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
930
|
}
|
|
872
931
|
|
|
873
932
|
function copyRunner() {
|
package/commands/shifu-sync.md
CHANGED
|
@@ -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 `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.
|
|
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
18
|
|
|
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
|
|
19
|
+
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
|
|
|
@@ -64,6 +64,8 @@ For an explicit sync request, first read `status` to determine the saved approva
|
|
|
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",
|