@useshifu/coding-harness 0.3.31 → 0.3.32
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
CHANGED
|
@@ -44,6 +44,8 @@ To disconnect this device, run `npx --yes @useshifu/coding-harness disconnect --
|
|
|
44
44
|
|
|
45
45
|
## Operate
|
|
46
46
|
|
|
47
|
+
Version `0.3.32` preserves failed and pending source status during preparation, distinguishes people by their original source occurrences, and keeps the two-attempt review limit across metadata changes. Existing queues, V5 payloads and receipts remain compatible.
|
|
48
|
+
|
|
47
49
|
Version `0.3.30` adds separately versioned episode and entity continuity without changing the V5 sync body or older queued payloads. Preparation uses bounded prior-turn references, source coverage checks and durable two-attempt review holds. Base acceptance and attachment completion have separate status. See [V5 continuity](#continuity-beside-version-five) below for privacy, recovery and mixed-version operation.
|
|
48
50
|
|
|
49
51
|
Version `0.3.29` verifies native v5 sync across Codex, Claude Code, OpenCode 1 and OpenCode 2. Codex now loads a private allowlisted configuration and home instead of an ignored profile, with executable tools disabled. OpenCode 2 uses its standalone CLI, native provider/agent configuration, typed session database and isolated account storage. Native CI tests exercise original-source discovery, extraction, redaction, frozen retries and matching receipt checkpoints against local synthetic endpoints, without live credentials or paid models.
|
|
@@ -128,7 +130,7 @@ The AI Profile page shows detected categories, model and thinking labels, and Je
|
|
|
128
130
|
|
|
129
131
|
The connection endpoint returns `recommendedPackageVersion`, pinned to a published release. Before `sync`, `sync-files`, `approve`, or `scheduled-sync`, the runner compares its embedded release version with that property. An older runner installs the exact recommended release through npm and resumes the original command with the updated runner. Equal or newer runners continue unchanged. Explicitly recommended major releases follow the same comparison rule. Missing or invalid recommendations, connection failures, and npm failures do not block sync. The connection, pending work, checkpoints, and schedule are preserved.
|
|
130
132
|
|
|
131
|
-
This update mechanism first ships in `0.3.10`. Runners from earlier releases need a one-time installation of a release containing it (`npx --yes @useshifu/coding-harness@0.3.
|
|
133
|
+
This update mechanism first ships in `0.3.10`. Runners from earlier releases need a one-time installation of a release containing it (`npx --yes @useshifu/coding-harness@0.3.32 install --harness codex`). Raise the server recommendation only after the target release is published and its backend requirements are deployed.
|
|
132
134
|
|
|
133
135
|
## Force sync local work
|
|
134
136
|
|
|
@@ -163,7 +165,7 @@ Setup preserves other instruction text and MCP settings. Package installation re
|
|
|
163
165
|
The context branch adds a remote MCP connection using the same saved Codex key:
|
|
164
166
|
|
|
165
167
|
```sh
|
|
166
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
168
|
+
npx --yes @useshifu/coding-harness@0.3.32 mcp-setup --harness codex --mcp-url https://YOUR-BRANCH-HOST/mcp
|
|
167
169
|
```
|
|
168
170
|
|
|
169
171
|
For development, install dependencies and run from the checkout root:
|
|
@@ -194,7 +196,7 @@ Official configuration reference: https://learn.chatgpt.com/docs/config-file/con
|
|
|
194
196
|
OpenCode uses the same `/mcp` endpoint and three tools, authenticated with its own saved OpenCode connection key:
|
|
195
197
|
|
|
196
198
|
```sh
|
|
197
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
199
|
+
npx --yes @useshifu/coding-harness@0.3.32 mcp-setup --harness opencode --mcp-url https://YOUR-SHIFU-HOST/mcp
|
|
198
200
|
```
|
|
199
201
|
|
|
200
202
|
For checkout usage, install dependencies with `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` from the checkout root, then run the checkout CLI with the same arguments. Setup preserves existing sync settings and other MCP servers, checks authenticated initialization and tool discovery, and installs the `shifu-context` usage instructions. A missing OpenCode connection prompts for its key in the terminal. A Codex key cannot authorize an OpenCode connection.
|
|
@@ -214,7 +216,7 @@ For the optional native integration gate, set `SHIFU_TEST_OPENCODE_BIN` to a tem
|
|
|
214
216
|
Claude Code connects to the same `/mcp` endpoint and three tools with its own saved connection key:
|
|
215
217
|
|
|
216
218
|
```sh
|
|
217
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
219
|
+
npx --yes @useshifu/coding-harness@0.3.32 mcp-setup --harness claude_code --mcp-url https://YOUR-SHIFU-HOST/mcp
|
|
218
220
|
```
|
|
219
221
|
|
|
220
222
|
For checkout usage, install dependencies as above and run the checkout CLI with these arguments. Setup checks the existing Claude Code key, authenticated initialization and tool discovery, and installs the `shifu-context` skill. It preserves the sync endpoint, schedule, checkpoints and other MCP servers. A missing connection prompts for a Claude Code key in the terminal; do not substitute a Codex or OpenCode key.
|
|
@@ -234,11 +236,11 @@ Cursor support ships in `0.3.31` and requires backend migration `071_cursor_harn
|
|
|
234
236
|
Install [Cursor Agent CLI](https://cursor.com/docs/cli/installation) and sign in with `agent login`. The desktop app's login does not necessarily authenticate the CLI. Then create a dedicated Cursor key in Shifu and run:
|
|
235
237
|
|
|
236
238
|
```sh
|
|
237
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
238
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
239
|
+
npx --yes @useshifu/coding-harness@0.3.32 connect --harness cursor
|
|
240
|
+
npx --yes @useshifu/coding-harness@0.3.32 mcp-setup --harness cursor
|
|
239
241
|
```
|
|
240
242
|
|
|
241
|
-
For a checkout before publication, run `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` and replace `npx --yes @useshifu/coding-harness@0.3.
|
|
243
|
+
For a checkout before publication, run `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` and replace `npx --yes @useshifu/coding-harness@0.3.32` with `node packages/shifu-coding-harness/bin/shifu-harness.js`. `connect` installs the runner, skills and work-capture hooks, verifies the Cursor-specific key, saves history and feedback choices, and configures the existing sync schedule. MCP setup verifies authenticated initialization and discovery, preserves those choices and other MCP entries, and adds a local stdio bridge in `~/.cursor/mcp.json`. The bridge reads the protected Shifu connection file; no key is copied into Cursor configuration or process arguments. A conflicting existing `shifu` entry is preserved with a rename instruction. Other harness keys cannot authorize Cursor.
|
|
242
244
|
|
|
243
245
|
Reload Cursor and start a new chat. Enable the Shifu MCP server if Cursor requests approval; CLI users can run `agent mcp enable shifu` and `agent mcp list-tools shifu`. Normal tool approvals still apply. Setup's HTTP probe is separate from native-client discovery and a model-driven tool call.
|
|
244
246
|
|
|
@@ -15,6 +15,7 @@ schema.properties.evidence.items.properties.episode = { anyOf: [{ type: "null" }
|
|
|
15
15
|
schema.properties.evidence.items.required.push("contextRefs", "entityRefs", "episode");
|
|
16
16
|
schema.properties.entities = { type: "array", maxItems: 32, items: exactSchema({ ref: opaque, kind: { type: "string", enum: KINDS },
|
|
17
17
|
visibility: { type: "string", enum: ["public", "private"] }, sourceRef: opaque, mention: { type: "string", minLength: 1, maxLength: 160 },
|
|
18
|
+
mentionIndex: { type: "integer", minimum: 0, maximum: 262144 },
|
|
18
19
|
bindingRef: { type: "string", maxLength: 80 }, publicKey: { type: "string", enum: ["", ...Object.keys(CATALOG)] } }) };
|
|
19
20
|
schema.properties.assessment = exactSchema({ disposition: { type: "string", enum: ["useful", "no_safe_context", "unresolved"] },
|
|
20
21
|
emptyReason: { type: "string", enum: ["none", "no_reusable_material", "privacy_withheld", "non_work_noise"] },
|
|
@@ -29,7 +30,7 @@ Return the additional entities and assessment fields and per-item contextRefs, e
|
|
|
29
30
|
|
|
30
31
|
Every emitted fact must have current sourceRefs. contextRefs may contain only preceding referenceSources locators. episode is null when no episode applies; otherwise anchorRef must identify an actual current or reference source, preferably the original proposal anchor when it is within lookback; stage must distinguish proposal, requested, reported, pending, revised or rejected. Repeated topic alone is not an episode.
|
|
31
32
|
|
|
32
|
-
Preserve relevant technology names only through code-permitted public bindings: ${JSON.stringify(CATALOG)}. Every material selected named entity gets an entities entry with a short unique ref, kind, visibility, sourceRef
|
|
33
|
+
Preserve relevant technology names only through code-permitted public bindings: ${JSON.stringify(CATALOG)}. Every material selected named entity gets an entities entry with a short unique ref, kind, visibility, sourceRef, its exact source mention and mentionIndex (zero-based occurrence of that exact whole-word mention in that source). publicKey must be an allowed key for public technology and bindingRef empty; otherwise publicKey is empty and visibility private. For private identity, match packet.entityBindings sourceAnchors against the original sourceRef and mentionIndex, using the original surrounding words to distinguish roles and people. Reuse that explicit bindingRef for a known original occurrence; an empty bindingRef is only for a genuinely new occurrence. Two people called Alex in one message have different mentionIndex values and must keep their original bindings. Equal names alone do not establish identity; ambiguous identities are unresolved. When the current message refers to their earlier roles, identify the person through that preceding original source occurrence rather than creating a new binding from the repeated name. Confidential employer/customer relationships remain private even when the same word names public technology. The code, not you, assigns public labels and private aliases. Use {{ref}} placeholders for every entity mention in title, summary, decisions, verification, statement and workContext; never output raw private names there. Private placeholders expand to about fifty characters; keep enough room under all text limits after binding. Each evidence item lists pertinent entityRefs only. Entity source mention and raw identity are local-only and must not appear anywhere else in the result. Source role is authorship, never personal ownership.
|
|
33
34
|
|
|
34
35
|
Assessment coverage accounts for every current source locator exactly once. represented entries identify distinct evidence indexes actually citing that source; irrelevant and privacy_withheld entries have no evidence indexes. A material omitted decision, pending check, constraint or ambiguity is unresolved. For useful output every decision and verification entry must have a pertinent evidence reference. Empty evidence cannot coexist with decisions or verification. no_safe_context requires an explicit reason and a neutral nonfactual title and summary; do not erase meaningful work merely to produce an empty result. unresolved is valid and keeps this range pending. Never create filler or require one claim per message.\n`;
|
|
35
36
|
}
|
|
@@ -58,14 +59,41 @@ function metadata(source) {
|
|
|
58
59
|
return { role: source.role, turn: source.turn, locator: source.locator, ...(source.eventAt ? { eventAt: source.eventAt } : {}), excerptMode: "synthesized" };
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function
|
|
62
|
+
function sourceMentionOffsets(content, mention) {
|
|
62
63
|
const characters = [...mention];
|
|
63
64
|
const word = /[\p{L}\p{N}\p{M}_]/u;
|
|
64
65
|
const boundary = "[\\p{L}\\p{N}\\p{M}_]";
|
|
65
66
|
const escaped = mention.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
66
67
|
const before = word.test(characters[0]) ? `(?<!${boundary})` : "";
|
|
67
68
|
const after = word.test(characters.at(-1)) ? `(?!${boundary})` : "";
|
|
68
|
-
return new RegExp(before + escaped + after, "
|
|
69
|
+
return [...content.matchAll(new RegExp(before + escaped + after, "gu"))].map((match) => match.index);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sameSourceAnchor(first, second) {
|
|
73
|
+
return first.sessionRef === second.sessionRef && first.sourceRef === second.sourceRef && first.sourceDigest === second.sourceDigest && first.offset === second.offset;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function availableSourceAnchors(packet, entry) {
|
|
77
|
+
const sources = [...packet.sources, ...(packet.referenceSources || [])];
|
|
78
|
+
// Legacy createdBy records the preparation range start, not the entity's original source.
|
|
79
|
+
return (entry.sourceAnchors || []).filter((anchor) => sources.some((source) =>
|
|
80
|
+
source.locator === anchor.sourceRef && anchor.sessionRef === packet.sessionRef && anchor.sourceDigest === digest(source.content) &&
|
|
81
|
+
sourceMentionOffsets(source.content, entry.mention).includes(anchor.offset)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function relevantEntityBindings(packet, scope) {
|
|
85
|
+
const sources = new Map([...packet.sources, ...(packet.referenceSources || [])].map((source) => [source.locator, source]));
|
|
86
|
+
const entities = Object.values(scope.entities).map((entry) => ({ entry, anchors: availableSourceAnchors(packet, entry) }));
|
|
87
|
+
return entities.filter(({ entry }) => entry.createdBy !== `${packet.sessionRef}:${packet.fromTurn}`).flatMap(({ entry, anchors }) => {
|
|
88
|
+
const sourceAnchors = anchors.flatMap((anchor) => {
|
|
89
|
+
if (entities.filter((other) => other.anchors.some((candidate) => sameSourceAnchor(candidate, anchor))).length !== 1) return [];
|
|
90
|
+
const source = sources.get(anchor.sourceRef);
|
|
91
|
+
if (!source || anchor.sessionRef !== packet.sessionRef || anchor.sourceDigest !== digest(source.content)) return [];
|
|
92
|
+
const mentionIndex = sourceMentionOffsets(source.content, entry.mention).indexOf(anchor.offset);
|
|
93
|
+
return mentionIndex < 0 ? [] : [{ sourceRef: source.locator, mentionIndex, role: source.role, turn: source.turn }];
|
|
94
|
+
});
|
|
95
|
+
return sourceAnchors.length ? [{ bindingRef: entry.key, mention: entry.mention, kind: entry.kind, sourceAnchors }] : [];
|
|
96
|
+
});
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
function bindContinuity(packet, output, dictionary, workspace) {
|
|
@@ -80,12 +108,17 @@ function bindContinuity(packet, output, dictionary, workspace) {
|
|
|
80
108
|
dictionary.scopes ||= {};
|
|
81
109
|
const scope = dictionary.scopes[workspace] ||= { scopeRef: randomKey("s_"), entities: {} };
|
|
82
110
|
for (const entity of array(output.entities, 32)) {
|
|
83
|
-
|
|
111
|
+
const fields = ["ref", "kind", "visibility", "sourceRef", "mention", "bindingRef", "publicKey"];
|
|
112
|
+
exact(entity, Object.hasOwn(entity, "mentionIndex") ? [...fields, "mentionIndex"] : fields);
|
|
84
113
|
const source = all.get(entity.sourceRef);
|
|
114
|
+
const offsets = source && typeof entity.mention === "string" && entity.mention.trim() ? sourceMentionOffsets(source.content, entity.mention) : [];
|
|
85
115
|
if (typeof entity.ref !== "string" || !/^[A-Za-z0-9_-]{1,80}$/.test(entity.ref) || selected.has(entity.ref) || !KINDS.includes(entity.kind) ||
|
|
86
|
-
!source || typeof entity.mention !== "string" || !entity.mention.trim() || [...entity.mention].length > 160 || !
|
|
116
|
+
!source || typeof entity.mention !== "string" || !entity.mention.trim() || [...entity.mention].length > 160 || !offsets.length) {
|
|
87
117
|
throw new Error("Entity binding is not grounded in a supplied source mention.");
|
|
88
118
|
}
|
|
119
|
+
if (Object.hasOwn(entity, "mentionIndex") && (!Number.isSafeInteger(entity.mentionIndex) || entity.mentionIndex < 0 || entity.mentionIndex >= offsets.length)) {
|
|
120
|
+
throw new Error("Entity binding selected an invalid source mention occurrence.");
|
|
121
|
+
}
|
|
89
122
|
let binding;
|
|
90
123
|
if (entity.visibility === "public") {
|
|
91
124
|
if (entity.bindingRef !== "" || entity.kind !== "technology" || !Object.hasOwn(CATALOG, entity.publicKey) || entity.mention.toLocaleLowerCase() !== CATALOG[entity.publicKey].toLocaleLowerCase()) {
|
|
@@ -93,14 +126,24 @@ function bindContinuity(packet, output, dictionary, workspace) {
|
|
|
93
126
|
}
|
|
94
127
|
binding = { key: entity.publicKey, scopeRef: "public", kind: "technology", visibility: "public", label: CATALOG[entity.publicKey] };
|
|
95
128
|
} else if (entity.visibility === "private" && entity.publicKey === "") {
|
|
129
|
+
const mentionIndex = entity.mentionIndex ?? (offsets.length === 1 ? 0 : undefined);
|
|
130
|
+
if (!Number.isSafeInteger(mentionIndex) || mentionIndex < 0 || mentionIndex >= offsets.length) throw new Error("Private identity needs an unambiguous source mention occurrence.");
|
|
131
|
+
const anchor = { sessionRef: packet.sessionRef, sourceRef: source.locator, sourceDigest: digest(source.content), offset: offsets[mentionIndex] };
|
|
96
132
|
let key = entity.bindingRef;
|
|
97
133
|
if (typeof key !== "string") throw new Error("Private identity needs an explicit local binding selection.");
|
|
98
134
|
if (key) {
|
|
99
135
|
const known = scope.entities[key];
|
|
100
|
-
if (!known || known.kind !== entity.kind || known.mention.normalize("NFC").toLocaleLowerCase() !== entity.mention.normalize("NFC").toLocaleLowerCase() ||
|
|
136
|
+
if (!known || known.kind !== entity.kind || known.mention.normalize("NFC").toLocaleLowerCase() !== entity.mention.normalize("NFC").toLocaleLowerCase() ||
|
|
137
|
+
!(packet.entityBindings || []).some((entry) => entry.bindingRef === key) || !relevantEntityBindings(packet, scope).some((entry) => entry.bindingRef === key) ||
|
|
138
|
+
!availableSourceAnchors(packet, known).some((knownAnchor) => sameSourceAnchor(knownAnchor, anchor))) {
|
|
139
|
+
throw new Error("Private identity selected an unavailable or conflicting source binding.");
|
|
140
|
+
}
|
|
101
141
|
} else {
|
|
142
|
+
if (Object.values(scope.entities).some((known) => availableSourceAnchors(packet, known).some((knownAnchor) => sameSourceAnchor(knownAnchor, anchor)))) {
|
|
143
|
+
throw new Error("This private source occurrence already has a binding; select it explicitly or leave the identity unresolved.");
|
|
144
|
+
}
|
|
102
145
|
key = randomKey("e_");
|
|
103
|
-
scope.entities[key] = { key, mention: entity.mention, kind: entity.kind, createdBy: `${packet.sessionRef}:${packet.fromTurn}
|
|
146
|
+
scope.entities[key] = { key, mention: entity.mention, kind: entity.kind, createdBy: `${packet.sessionRef}:${packet.fromTurn}`, sourceAnchors: [anchor] };
|
|
104
147
|
}
|
|
105
148
|
binding = { key, scopeRef: scope.scopeRef, kind: entity.kind, visibility: "private", label: `${entity.kind}_${key}` };
|
|
106
149
|
} else throw new Error("Entity binding requested an invalid visibility.");
|
|
@@ -183,4 +226,4 @@ function bindContinuity(packet, output, dictionary, workspace) {
|
|
|
183
226
|
assessment: { disposition: assessment.disposition, emptyReason: assessment.emptyReason, currentSources: current.size, referenceSources: references.size } };
|
|
184
227
|
}
|
|
185
228
|
|
|
186
|
-
module.exports = { CONTINUITY_SCHEMA: schema, POLICY_VERSION, CATALOG, continuityPrompt, bindContinuity, digest };
|
|
229
|
+
module.exports = { CONTINUITY_SCHEMA: schema, POLICY_VERSION, CATALOG, continuityPrompt, bindContinuity, relevantEntityBindings, digest };
|
package/bin/continuity-state.js
CHANGED
|
@@ -92,13 +92,12 @@ function continuityStore(root, harness, config) {
|
|
|
92
92
|
const prior = state.attempts[identity];
|
|
93
93
|
if (prior?.payloadKey) return { prepared: state.outbox.find((entry) => entry.key === prior.payloadKey) };
|
|
94
94
|
if (prior && prior.toTurn !== packet.toTurn) throw new Error("The unresolved preparation range remains pinned.");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
attempt.status = "needs_review";
|
|
98
|
-
state.attempts[identity] = attempt;
|
|
95
|
+
if (prior?.attempts >= 2) {
|
|
96
|
+
prior.status = "needs_review";
|
|
99
97
|
save(state);
|
|
100
|
-
throw new Error("Continuity preparation needs review;
|
|
98
|
+
throw new Error("Continuity preparation needs review; this range exhausted two native attempts. Retry it explicitly after review.");
|
|
101
99
|
}
|
|
100
|
+
const attempt = prior?.fingerprint === fingerprint ? prior : { fingerprint, fromTurn: packet.fromTurn, toTurn: packet.toTurn, attempts: prior?.attempts || 0 };
|
|
102
101
|
attempt.attempts += 1;
|
|
103
102
|
attempt.status = "preparing";
|
|
104
103
|
state.attempts[identity] = attempt;
|
package/bin/shifu-harness.js
CHANGED
|
@@ -47,7 +47,7 @@ const MAX_TURNS_PER_SEGMENT = 12;
|
|
|
47
47
|
const MAX_TITLE_LENGTH = 160;
|
|
48
48
|
const MAX_WORK_DETAIL_LENGTH = 600;
|
|
49
49
|
const CONFIG_VERSION = 2;
|
|
50
|
-
const RUNNER_VERSION = "0.3.
|
|
50
|
+
const RUNNER_VERSION = "0.3.32";
|
|
51
51
|
const REDACTION_VERSION = 5;
|
|
52
52
|
const LEGACY_REDACTION_VERSION = 4;
|
|
53
53
|
const MAX_PREPARATIONS_PER_RUN = 4;
|
|
@@ -2647,9 +2647,7 @@ async function prepareV5Payload(harness, segment, prepare = require("./native-pr
|
|
|
2647
2647
|
dictionary.scopes[workspace] ||= { scopeRef: `s_${randomUUID().replaceAll("-", "")}`, entities: {} };
|
|
2648
2648
|
store.saveDictionary(dictionary);
|
|
2649
2649
|
const relevantBindings = () => {
|
|
2650
|
-
|
|
2651
|
-
return Object.values(dictionary.scopes[workspace].entities).filter((entry) => entry.createdBy !== `${packet.sessionRef}:${packet.fromTurn}` && content.includes(entry.mention.toLocaleLowerCase()))
|
|
2652
|
-
.map(({ key, mention, kind }) => ({ bindingRef: key, mention, kind }));
|
|
2650
|
+
return require("./continuity-preparation.js").relevantEntityBindings(packet, dictionary.scopes[workspace]);
|
|
2653
2651
|
};
|
|
2654
2652
|
packet.entityBindings = relevantBindings();
|
|
2655
2653
|
while (packet.referenceSources.length && Buffer.byteLength(JSON.stringify(packet)) + 1 > 256 * 1024) {
|
|
@@ -2712,6 +2710,7 @@ async function retryPreparation(harness) {
|
|
|
2712
2710
|
|
|
2713
2711
|
function preparationValidationFeedback(error, output) {
|
|
2714
2712
|
const fields = [["title", output?.title, 160], ["summary", output?.summary, 1100]];
|
|
2713
|
+
const issues = [];
|
|
2715
2714
|
for (const name of ["decisions", "verification"]) {
|
|
2716
2715
|
if (Array.isArray(output?.[name])) output[name].forEach((value, index) => fields.push([`${name}[${index}]`, value, 240]));
|
|
2717
2716
|
}
|
|
@@ -2734,9 +2733,9 @@ function preparationValidationFeedback(error, output) {
|
|
|
2734
2733
|
if (fragment) reason = `the forbidden literal fragment ${JSON.stringify(fragment)}`;
|
|
2735
2734
|
}
|
|
2736
2735
|
// Field names and rule literals are code-owned; never echo rejected text.
|
|
2737
|
-
|
|
2736
|
+
issues.push(`Field ${field} fails the privacy or length rule for ${reason}.`);
|
|
2738
2737
|
}
|
|
2739
|
-
return error.message;
|
|
2738
|
+
return `${error.message}${issues.length ? ` ${issues.join(" ")} Rephrase those fields without copying the rejected content.` : ""} Recheck every text field before returning. Describe a user's go-ahead as approval, permission or adoption; the literal word authorization is rejected by the privacy filter. Preserve supported results, failed or pending checks, unresolved problems and release status while correcting the validation error.`;
|
|
2740
2739
|
}
|
|
2741
2740
|
|
|
2742
2741
|
function preparationState(harness, queue) {
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ Before first connection or MCP personalization, ask for any choices that are not
|
|
|
12
12
|
When asked to connect Shifu context, run:
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npx --yes @useshifu/coding-harness@0.3.
|
|
15
|
+
npx --yes @useshifu/coding-harness@0.3.32 mcp-setup --harness codex
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
MCP setup verifies the connection and tools, saves the choices and installs instructions; it does not sync sessions or upload the selected history. History selection governs eligible work for subsequent sync. Do not start sync as part of this setup unless the user separately asks for it. If a saved connection exists, reuse its key and do not run `connect` again.
|