@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.
@@ -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 || typeof input.sessionRef !== "string" || !textIsSafe(input.sessionRef, 200) || input.sessionRef.length < 8) throw new Error("Add the current opaque sessionRef before syncing.");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useshifu/coding-harness",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Reviewed, incremental coding-harness sync for Shifu",
5
5
  "bin": {
6
6
  "coding-harness": "bin/shifu-harness.js",
@@ -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
  {