@useshifu/coding-harness 0.3.24 → 0.3.26
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 +38 -16
- package/bin/context-preferences.js +68 -0
- package/bin/initial-history.js +32 -0
- package/bin/native-preparation.js +261 -0
- package/bin/onboarding-ui.js +143 -0
- package/bin/setup-options.js +39 -0
- package/bin/shifu-harness.js +389 -151
- package/bin/source-review.js +101 -9
- package/bin/v5-payload.js +112 -0
- package/commands/shifu-sync.md +11 -7
- package/package.json +3 -2
- package/skills/shifu-context/SKILL.md +19 -7
- package/skills/shifu-sync/SKILL.md +48 -24
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const HARNESS_NAMES = {
|
|
2
|
+
codex: "Codex",
|
|
3
|
+
claude_code: "Claude Code",
|
|
4
|
+
opencode: "OpenCode",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
function presentation({ isTTY = false, columns = 80, env = process.env } = {}) {
|
|
8
|
+
const rich = isTTY && env.TERM !== "dumb" && columns >= 64;
|
|
9
|
+
return { rich, color: rich && env.NO_COLOR === undefined, width: Math.max(24, Math.min(60, columns - 4)) };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function heading(subtitle, terminal) {
|
|
13
|
+
const { rich, color, width } = presentation(terminal);
|
|
14
|
+
const title = "S H I F U";
|
|
15
|
+
const wordmark = color ? `\u001b[36m${title}\u001b[0m` : title;
|
|
16
|
+
if (!rich) return `\n ${wordmark}\n ${subtitle}\n`;
|
|
17
|
+
const line = "─".repeat(width);
|
|
18
|
+
return `\n ╭${line}╮\n │${" ".repeat(width)}│\n │ ${wordmark}${" ".repeat(width - title.length - 3)}│\n │${" ".repeat(width)}│\n ╰${line}╯\n\n ${subtitle}\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function text(lines, terminal) {
|
|
22
|
+
const { width } = presentation(terminal);
|
|
23
|
+
return lines.map((line) => {
|
|
24
|
+
if (!line) return "";
|
|
25
|
+
const words = line.split(" ");
|
|
26
|
+
const wrapped = [];
|
|
27
|
+
let current = "";
|
|
28
|
+
for (const word of words) {
|
|
29
|
+
if (current && current.length + word.length + 1 > width) {
|
|
30
|
+
wrapped.push(` ${current}`);
|
|
31
|
+
current = "";
|
|
32
|
+
}
|
|
33
|
+
current += `${current ? " " : ""}${word}`;
|
|
34
|
+
}
|
|
35
|
+
wrapped.push(` ${current}`);
|
|
36
|
+
return wrapped.join("\n");
|
|
37
|
+
}).join("\n") + "\n";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderWelcome({ harness, upgraded = false } = {}, terminal) {
|
|
41
|
+
const rich = presentation(terminal).rich;
|
|
42
|
+
const name = HARNESS_NAMES[harness];
|
|
43
|
+
return heading("Your work has a history. Put it to work.", terminal) + text([
|
|
44
|
+
"",
|
|
45
|
+
`${rich ? "✓" : "[ok]"} Package ${upgraded ? "updated" : "installed"}${name ? ` for ${name}` : ""}`,
|
|
46
|
+
"",
|
|
47
|
+
rich ? "Your work ──→ Shifu ──→ Context for what comes next" : "Your work --> Shifu --> Context for what comes next",
|
|
48
|
+
"",
|
|
49
|
+
"RECOMMENDED SETUP",
|
|
50
|
+
"",
|
|
51
|
+
"Automatic work sync: every hour",
|
|
52
|
+
"Saves redacted work summaries to your Shifu account, keeping decisions, lessons and their reasoning available for future conversations.",
|
|
53
|
+
"",
|
|
54
|
+
"Files placed in your Shifu folder are uploaded separately for processing.",
|
|
55
|
+
"",
|
|
56
|
+
"We recommend automatic sync to keep your context current. You can change the schedule or choose manual approval for work summaries anytime. Just ask your assistant.",
|
|
57
|
+
"",
|
|
58
|
+
upgraded ? "Your saved connection and settings are preserved." : "Next, connect your account and choose how Shifu helps.",
|
|
59
|
+
"",
|
|
60
|
+
], terminal);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderSetupIntro(terminal) {
|
|
64
|
+
return heading("Personalize how Shifu helps.", terminal) + text([
|
|
65
|
+
"",
|
|
66
|
+
"HISTORICAL CONTEXT: always on",
|
|
67
|
+
"Your assistant retrieves relevant past decisions, discussions and lessons when a task or new topic begins.",
|
|
68
|
+
"",
|
|
69
|
+
"HELPFUL GROWTH FEEDBACK",
|
|
70
|
+
"Offer an encouraging suggestion when there is a useful opportunity to improve how you approach your work.",
|
|
71
|
+
"Example: Shifu: You made the trade-off clear. Adding a success measure could make your recommendation even stronger.",
|
|
72
|
+
"",
|
|
73
|
+
"DECISION ALIGNMENT",
|
|
74
|
+
"Highlight when a proposed or completed action supports earlier agreements, or differs from them.",
|
|
75
|
+
"Example: Shifu: This matches the 30-minute notification window agreed in your earlier meeting.",
|
|
76
|
+
"Example: Shifu: Earlier notes mention 30 minutes; this proposal uses an hour. Worth checking whether that still applies.",
|
|
77
|
+
"",
|
|
78
|
+
"We recommend both. Choose each independently, and change either anytime by asking your assistant.",
|
|
79
|
+
"Your current instructions always take priority. Feedback appears when it is useful and grounded in your work.",
|
|
80
|
+
"",
|
|
81
|
+
], terminal);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function historyLabel(history) {
|
|
85
|
+
if (history?.status === "skipped" || history?.days === 0) return "Start from now";
|
|
86
|
+
if (!Number.isInteger(history?.days) || history.days < 1) return "Not selected";
|
|
87
|
+
const window = `Last ${history.days} days`;
|
|
88
|
+
if (history.status === "complete") return `${window} - completed`;
|
|
89
|
+
if (history.status === "awaiting") return `${window} - awaiting sync`;
|
|
90
|
+
return `${window} - selected; not synced during setup`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function syncLabel(policy) {
|
|
94
|
+
if (!policy) return "Not configured";
|
|
95
|
+
if (policy.scheduleEnabled === false) return "Schedule off";
|
|
96
|
+
const hours = policy.intervalHours;
|
|
97
|
+
if (!Number.isFinite(hours) || hours <= 0) return "Not configured";
|
|
98
|
+
const interval = hours === 1 ? "Every hour" : `Every ${hours} hours`;
|
|
99
|
+
return `${interval} - ${policy.approvalMode === "automatic" ? "automatic" : "manual approval"}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderConnected({ harness, contextPreferences = {}, initialBackfill, syncPolicy, mcpVerified = false, reload = false } = {}, terminal) {
|
|
103
|
+
const marker = presentation(terminal).rich ? "✓" : "[ok]";
|
|
104
|
+
const name = HARNESS_NAMES[harness] || "your assistant";
|
|
105
|
+
return heading(mcpVerified ? "Connected. Ready to help." : "Account connected. Finish MCP setup to load context.", terminal) + text([
|
|
106
|
+
"",
|
|
107
|
+
`${marker} Account connection verified`,
|
|
108
|
+
...(mcpVerified ? [`${marker} MCP tools discovered`, `${marker} Preferences saved`] : []),
|
|
109
|
+
"",
|
|
110
|
+
"YOUR SETUP",
|
|
111
|
+
"",
|
|
112
|
+
`Historical context: ${mcpVerified ? "On - included" : "Awaiting MCP setup"}`,
|
|
113
|
+
`Growth feedback: ${contextPreferences.growthFeedback === false ? "Off" : "On"}`,
|
|
114
|
+
`Decision alignment: ${contextPreferences.decisionAlignment === false ? "Off" : "On"}`,
|
|
115
|
+
`Work-summary sync: ${syncLabel(syncPolicy)}`,
|
|
116
|
+
`Initial history: ${historyLabel(initialBackfill)}`,
|
|
117
|
+
"",
|
|
118
|
+
"CHANGE YOUR MIND ANYTIME",
|
|
119
|
+
"Just ask your assistant:",
|
|
120
|
+
'"Turn off growth feedback, but keep decision alignment."',
|
|
121
|
+
'"Switch work-summary sync to manual approval."',
|
|
122
|
+
"",
|
|
123
|
+
...(mcpVerified && reload ? [typeof reload === "string" ? reload : `Reload ${name} or start a new chat to load Shifu.`, ""] : []),
|
|
124
|
+
], terminal);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function terminalFor(output) {
|
|
128
|
+
return { isTTY: output.isTTY === true, columns: output.columns || 80, env: process.env };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function printWelcome(options, output = process.stderr) {
|
|
132
|
+
output.write(renderWelcome(options, terminalFor(output)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function printSetupIntro(output = process.stderr) {
|
|
136
|
+
output.write(renderSetupIntro(terminalFor(output)));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function printConnected(options, output = process.stderr) {
|
|
140
|
+
output.write(renderConnected(options, terminalFor(output)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { renderWelcome, renderSetupIntro, renderConnected, printWelcome, printSetupIntro, printConnected };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { normalizeContextPreferences } = require("./context-preferences.js");
|
|
2
|
+
const { createInitialBackfill } = require("./initial-history.js");
|
|
3
|
+
|
|
4
|
+
function toggle(value, name) {
|
|
5
|
+
if (value === undefined) return undefined;
|
|
6
|
+
if (value !== "on" && value !== "off") throw new Error(`Choose on or off for ${name}.`);
|
|
7
|
+
return value === "on";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function chooseSetupOptions(config, { option = () => undefined, ask } = {}) {
|
|
11
|
+
const preferences = normalizeContextPreferences(config?.contextPreferences);
|
|
12
|
+
const growth = toggle(option("--growth-feedback"), "growth feedback");
|
|
13
|
+
const alignment = toggle(option("--decision-alignment"), "decision alignment");
|
|
14
|
+
const days = option("--backfill-days");
|
|
15
|
+
let initialBackfill = days === undefined ? config?.initialBackfill : createInitialBackfill(days);
|
|
16
|
+
if (growth !== undefined) preferences.growthFeedback = growth;
|
|
17
|
+
if (alignment !== undefined) preferences.decisionAlignment = alignment;
|
|
18
|
+
if (ask) {
|
|
19
|
+
if (initialBackfill === undefined) {
|
|
20
|
+
const answer = (await ask("Initial history: [1] Last 15 days (recommended), [2] Last 30 days, [3] Custom days, [4] Start from now. Choose [1]: ")).trim();
|
|
21
|
+
if (!["", "1", "2", "3", "4"].includes(answer)) throw new Error("Choose history option 1, 2, 3 or 4. Nothing was changed.");
|
|
22
|
+
const selectedDays = answer === "3" ? (await ask("How many days? ")).trim() : answer === "2" ? 30 : answer === "4" ? 0 : 15;
|
|
23
|
+
initialBackfill = createInitialBackfill(selectedDays);
|
|
24
|
+
}
|
|
25
|
+
for (const [key, value, label] of [
|
|
26
|
+
["growthFeedback", growth, "Helpful growth feedback"],
|
|
27
|
+
["decisionAlignment", alignment, "Decision alignment"],
|
|
28
|
+
]) {
|
|
29
|
+
if (value !== undefined || config?.contextPreferences?.[key] !== undefined) continue;
|
|
30
|
+
const answer = (await ask(`${label}: On (recommended) / Off [On]: `)).trim().toLowerCase();
|
|
31
|
+
if (!["", "on", "off", "yes", "no", "y", "n"].includes(answer)) throw new Error(`Choose On or Off for ${label}. Nothing was changed.`);
|
|
32
|
+
preferences[key] = !["off", "no", "n"].includes(answer);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!config && initialBackfill === undefined) initialBackfill = createInitialBackfill(15);
|
|
36
|
+
return { contextPreferences: preferences, ...(initialBackfill === undefined ? {} : { initialBackfill }) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { chooseSetupOptions };
|