@useshifu/coding-harness 0.2.5 → 0.2.6
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/bin/shifu-harness.js +17 -2
- package/package.json +1 -1
- package/skills/shifu-sync/SKILL.md +1 -1
package/bin/shifu-harness.js
CHANGED
|
@@ -16,6 +16,8 @@ const HARNESS_NAMES = {
|
|
|
16
16
|
const TOKEN_PATTERN = /^cs_sk_[a-f0-9]{16}_[a-f0-9]{64}$/;
|
|
17
17
|
const SENSITIVE_PATTERNS = ["-----begin", "authorization", "sk-", "sk_", "ghp_", "gho_", "ghu_", "ghs_", "ghr_", "xox", "github_pat_", "glpat-", "ignore previous instructions", "ignore all previous instructions", "system prompt", "@", "://", "`", "/"];
|
|
18
18
|
const PHONE_NUMBER_PATTERN = /(?:\+?\d[\d .()-]{6,}\d)/i;
|
|
19
|
+
const OPAQUE_SESSION_REF_PATTERN = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|(?:ses|thread)_[a-z0-9_-]+)$/i;
|
|
20
|
+
const UNSAFE_SESSION_REF_PATTERNS = ["api_key", "authorization", "password", "@", "://", "`", "/"];
|
|
19
21
|
const UNSAFE_CODE_PATTERN = /\b(?:func|class|interface|struct|package|import|select|insert|update|delete|create\s+table)\s+[a-z_][a-z0-9_]*\s*(?:\(|\{|=|$)/i;
|
|
20
22
|
const CREDENTIAL_ASSIGNMENT_PATTERN = /\b(?:token|secret|password|api[_-]?key)\s*[:=]\s*\S+/i;
|
|
21
23
|
const CLOUD_CREDENTIAL_PATTERN = /\bAKIA[0-9A-Z]{16}\b|AIza[0-9A-Za-z_-]{20,}/;
|
|
@@ -513,10 +515,17 @@ function textIsSafe(value, maximum) {
|
|
|
513
515
|
return typeof value === "string" && value.trim().length > 0 && value.length <= maximum && !/[\x00-\x1f\x7f]/.test(value) && !PHONE_NUMBER_PATTERN.test(value) && !UNSAFE_CODE_PATTERN.test(value) && !CREDENTIAL_ASSIGNMENT_PATTERN.test(value) && !CLOUD_CREDENTIAL_PATTERN.test(value) && !SENSITIVE_PATTERNS.some((pattern) => value.toLowerCase().includes(pattern));
|
|
514
516
|
}
|
|
515
517
|
|
|
518
|
+
function sessionRefIsSafe(value) {
|
|
519
|
+
if (typeof value !== "string") return false;
|
|
520
|
+
const trimmed = value.trim();
|
|
521
|
+
if (UNSAFE_SESSION_REF_PATTERNS.some((pattern) => trimmed.toLowerCase().includes(pattern))) return false;
|
|
522
|
+
return trimmed.length >= 8 && trimmed.length <= 200 && (OPAQUE_SESSION_REF_PATTERN.test(trimmed) || textIsSafe(trimmed, 200));
|
|
523
|
+
}
|
|
524
|
+
|
|
516
525
|
function validateSync(input, harness) {
|
|
517
526
|
if (!input || Object.keys(input).some((field) => !SYNC_FIELDS.has(field))) throw new Error("Remove unsupported fields from the reviewed sync payload.");
|
|
518
|
-
if (!input || input.harness !== harness ||
|
|
519
|
-
if (!Number.isInteger(input.fromTurn) || !Number.isInteger(input.toTurn) || input.fromTurn < 1 || input.toTurn < input.fromTurn || input.toTurn - input.fromTurn >= MAX_TURNS_PER_SEGMENT) throw new Error("Use a valid incremental range of at most 12 turns.");
|
|
527
|
+
if (!input || input.harness !== harness || !sessionRefIsSafe(input.sessionRef)) throw new Error("Add the current opaque sessionRef before syncing.");
|
|
528
|
+
if (!Number.isInteger(input.fromTurn) || !Number.isInteger(input.toTurn) || input.fromTurn < 1 || input.toTurn < input.fromTurn || input.toTurn > 1_000_000 || input.toTurn - input.fromTurn >= MAX_TURNS_PER_SEGMENT) throw new Error("Use a valid incremental range of at most 12 turns.");
|
|
520
529
|
if (input.redactionVersion === 3 && !textIsSafe(input.title, MAX_TITLE_LENGTH)) throw new Error("Add a concise, redacted title for this reviewed sync.");
|
|
521
530
|
if (!textIsSafe(input.summary, 1200)) throw new Error("The summary is empty, too long, or contains sensitive content. Redact it before syncing.");
|
|
522
531
|
if (!Array.isArray(input.evidence) || input.evidence.length < 1 || input.evidence.length > MAX_EVIDENCE_ITEMS || !input.evidence.every((item) => item && EVIDENCE_KINDS.has(item.kind) && SCOPE_LEVELS.has(item.scope) && (input.redactionVersion !== 3 || textIsSafe(item.title, MAX_TITLE_LENGTH)) && textIsSafe(item.statement, MAX_WORK_DETAIL_LENGTH))) {
|
|
@@ -534,6 +543,12 @@ function validateSync(input, harness) {
|
|
|
534
543
|
item.verificationRefs.every((ref) => Number.isInteger(ref) && ref >= 0 && ref < input.verification.length))))) {
|
|
535
544
|
throw new Error("Each work item must use a supported area, safe itemRef, and distinct indexes into verification.");
|
|
536
545
|
}
|
|
546
|
+
const identities = new Set();
|
|
547
|
+
for (const item of input.evidence) {
|
|
548
|
+
const identity = item.itemRef || `${item.kind}:${item.scope}:${item.statement.trim()}`;
|
|
549
|
+
if (identities.has(identity)) throw new Error("Each work item must have a distinct identity.");
|
|
550
|
+
identities.add(identity);
|
|
551
|
+
}
|
|
537
552
|
if (input.redactionVersion !== 2 && input.redactionVersion !== 3) throw new Error("Use redactionVersion 3 for new reviewed syncs.");
|
|
538
553
|
}
|
|
539
554
|
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ Use detail proportionate to the work: retain high-level design, low-level contro
|
|
|
31
31
|
|
|
32
32
|
Do not install or rely on lifecycle hooks or background reminders. A sync is always initiated by an explicit user request.
|
|
33
33
|
|
|
34
|
-
Use this structure
|
|
34
|
+
Use this structure. Do not invent additional evidence fields; the sync API rejects fields outside its contract.
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|