@mrciphersmith/keryx 0.2.130 → 0.2.131

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.
Files changed (3) hide show
  1. package/dist/cli.js +121 -15
  2. package/dist/core.js +48 -4
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -24338,6 +24338,49 @@ var init_trusted_wrap_up = __esm(() => {
24338
24338
  consumed = new WeakSet;
24339
24339
  });
24340
24340
 
24341
+ // src/sac/workspace-reference.ts
24342
+ function requireWorkspaceReference(raw) {
24343
+ const normalized = normalizeWorkspaceReference(raw);
24344
+ if (!normalized.ok)
24345
+ throw new Error(normalized.reason);
24346
+ return normalized.uri;
24347
+ }
24348
+ function normalizeWorkspaceReference(raw) {
24349
+ const trimmed = raw.trim();
24350
+ if (trimmed.length === 0) {
24351
+ return { ok: false, reason: 'a workspace reference must be a non-empty workspace-relative path, e.g. "./src/harness/search"' };
24352
+ }
24353
+ if (UNSAFE_ABSOLUTE.test(trimmed) || UNSAFE_SCHEME.test(trimmed) || trimmed.includes("\\")) {
24354
+ return {
24355
+ ok: false,
24356
+ reason: `"${trimmed}" is not a workspace-relative path \u2014 an absolute path, a URL and a Windows path are all refused. Use a path inside the project, e.g. "./src/harness/search".`
24357
+ };
24358
+ }
24359
+ const withoutDotSlash = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
24360
+ const withoutTrailingSlash = withoutDotSlash.replace(/\/+$/, "");
24361
+ if (withoutTrailingSlash.length === 0) {
24362
+ return { ok: false, reason: `"${trimmed}" names the project root rather than a path inside it \u2014 name a directory or file, e.g. "./src/harness/search".` };
24363
+ }
24364
+ const segments = withoutTrailingSlash.split("/");
24365
+ if (segments.includes("..") || segments.includes(".")) {
24366
+ return { ok: false, reason: `"${trimmed}" contains a "." or ".." path segment \u2014 a workspace reference must stay inside the project.` };
24367
+ }
24368
+ const uri = `./${withoutTrailingSlash}`;
24369
+ if (!WORKSPACE_REFERENCE_PATTERN.test(uri)) {
24370
+ return {
24371
+ ok: false,
24372
+ reason: `"${trimmed}" is not a usable workspace reference. The stored form is "${uri}" if the characters are the problem: ` + 'each path segment may contain only letters, digits, ".", "_" and "-", and no ".." segment is allowed.'
24373
+ };
24374
+ }
24375
+ return { ok: true, uri };
24376
+ }
24377
+ var WORKSPACE_REFERENCE_PATTERN, UNSAFE_ABSOLUTE, UNSAFE_SCHEME;
24378
+ var init_workspace_reference = __esm(() => {
24379
+ WORKSPACE_REFERENCE_PATTERN = /^\.\/(?!(?:.*\/)?\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
24380
+ UNSAFE_ABSOLUTE = /^[/\\]/;
24381
+ UNSAFE_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
24382
+ });
24383
+
24341
24384
  // src/sac/index.ts
24342
24385
  import { access as access2, readFile as readFile50, realpath as realpath6 } from "fs/promises";
24343
24386
  import { createHash as createHash18 } from "crypto";
@@ -25085,11 +25128,12 @@ async function evaluateStrictSacGuard(input) {
25085
25128
  }
25086
25129
  var idPattern, subjectPattern, revisionPattern, correlationPattern, workspacePathPattern, receiptHashPattern, utcPattern, forbiddenPayloadKeys, normativeSchemaFiles, normativeSchemas, NORMATIVE_SCHEMA_DIR, SacReferenceError, trustedActors, roleRank;
25087
25130
  var init_sac = __esm(() => {
25131
+ init_workspace_reference();
25088
25132
  idPattern = /^[a-z][a-z0-9-]{2,63}$/;
25089
25133
  subjectPattern = /^(?:user|team|service|agent):[a-z0-9][a-z0-9._-]{0,127}$/;
25090
25134
  revisionPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
25091
25135
  correlationPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{15,255}$/;
25092
- workspacePathPattern = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
25136
+ workspacePathPattern = WORKSPACE_REFERENCE_PATTERN;
25093
25137
  receiptHashPattern = /^[a-f0-9]{64}$/;
25094
25138
  utcPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?Z$/;
25095
25139
  forbiddenPayloadKeys = new Set(["prompt", "transcript", "hiddenReasoning", "secret", "secrets", "rawContent"]);
@@ -51085,7 +51129,7 @@ import path47 from "path";
51085
51129
  // package.json
51086
51130
  var package_default = {
51087
51131
  name: "@mrciphersmith/keryx",
51088
- version: "0.2.130",
51132
+ version: "0.2.131",
51089
51133
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
51090
51134
  private: false,
51091
51135
  publishConfig: {
@@ -78293,6 +78337,7 @@ function toMcpTools(ops = METAPROJECT_OPERATIONS, adapterFor = (cwd) => createMe
78293
78337
  }
78294
78338
 
78295
78339
  // src/sac/fwk-service.ts
78340
+ init_workspace_reference();
78296
78341
  init_assembly();
78297
78342
  init_sac();
78298
78343
  init_workspace_service();
@@ -78303,6 +78348,7 @@ import { appendFile as appendFile7, mkdir as mkdir59, open as open2, readFile as
78303
78348
  import path174 from "path";
78304
78349
 
78305
78350
  // src/sac/policy-experiment.ts
78351
+ init_workspace_reference();
78306
78352
  import { createHash as createHash34, createHmac as createHmac2 } from "crypto";
78307
78353
 
78308
78354
  // src/sac/receipt-integrity.ts
@@ -78366,7 +78412,7 @@ function stableValue(value) {
78366
78412
  }
78367
78413
  var stableJson2 = (value) => JSON.stringify(stableValue(value));
78368
78414
  var sha2564 = (value) => createHash34("sha256").update(stableJson2(value), "utf8").digest("hex");
78369
- var workspacePathPattern2 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
78415
+ var workspacePathPattern2 = WORKSPACE_REFERENCE_PATTERN;
78370
78416
  var trustedOutcomes = new WeakMap;
78371
78417
  var trustedSandboxEvidence = new WeakMap;
78372
78418
  var trustedSandboxExecutions = new WeakMap;
@@ -78458,7 +78504,7 @@ var metadataOnly = (value) => {
78458
78504
  var nowIso2 = (now) => now().toISOString();
78459
78505
  var reportHashPattern = /^[a-f0-9]{64}$/;
78460
78506
  var immutableVersionPattern = /(?:^|[-_.:])(?:latest|next|head|main|develop)(?:$|[-_.:])/i;
78461
- var workspacePathPattern3 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
78507
+ var workspacePathPattern3 = WORKSPACE_REFERENCE_PATTERN;
78462
78508
  var policyExperimentConfigPath = [".metaproject", "context-operations", "policy-experiment", "config.json"];
78463
78509
  var stableValue2 = (value) => {
78464
78510
  if (Array.isArray(value))
@@ -79333,6 +79379,7 @@ async function reclaimStaleExternalSlates(cwd, now = () => new Date) {
79333
79379
  init_slate();
79334
79380
  init_workspace_resolve();
79335
79381
  init_redact();
79382
+ init_workspace_reference();
79336
79383
 
79337
79384
  // src/mcp/tools.ts
79338
79385
  import { randomUUID as randomUUID19 } from "crypto";
@@ -79545,7 +79592,8 @@ function buildToolRegistry() {
79545
79592
  const title = stringParam2(params, "title")?.trim() ?? "";
79546
79593
  if (title.length === 0)
79547
79594
  throw new Error("sac.workspaceCreate requires a non-empty 'title'");
79548
- const component = stringParam2(params, "component");
79595
+ const rawComponent = stringParam2(params, "component");
79596
+ const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
79549
79597
  const workspace = await sacWorkspaceService(cwd).create({ request: undefined, requestCorrelationId: randomUUID19(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
79550
79598
  return workspace;
79551
79599
  }
@@ -87467,7 +87515,12 @@ function workspaceCreateTool(cwd, getSessionDir) {
87467
87515
  const title = typeof input.title === "string" ? input.title.trim() : "";
87468
87516
  if (title.length === 0)
87469
87517
  return { output: "workspace_create requires a non-empty 'title'", isError: true };
87470
- const component = typeof input.component === "string" && input.component.length > 0 ? input.component : undefined;
87518
+ const rawComponent = typeof input.component === "string" && input.component.length > 0 ? input.component : undefined;
87519
+ const normalizedComponent = rawComponent === undefined ? undefined : normalizeWorkspaceReference(rawComponent);
87520
+ if (normalizedComponent !== undefined && !normalizedComponent.ok) {
87521
+ return { output: "workspace_create: " + normalizedComponent.reason, isError: true };
87522
+ }
87523
+ const component = normalizedComponent?.ok === true ? normalizedComponent.uri : undefined;
87471
87524
  try {
87472
87525
  const workspace = await service5(cwd).create({
87473
87526
  request: undefined,
@@ -93273,14 +93326,25 @@ async function classifySession(session) {
93273
93326
  sessionId: session.id,
93274
93327
  ...workspaceId !== undefined ? { workspaceId } : {},
93275
93328
  lastSeenAt: session.updatedAt,
93329
+ reason: "wrap-up-failed",
93276
93330
  wrapUpOutcome: { trigger: wrapUpOutcome.trigger, generatedAt: wrapUpOutcome.generatedAt, groups: wrapUpOutcome.groups }
93277
93331
  }
93278
93332
  };
93279
93333
  }
93280
93334
  if (!await isSlateEngaged(dir))
93281
93335
  return;
93282
- const workspaceId = (await safeReadSlate(dir))?.workspaceId;
93283
- return { kind: "unknown", item: { type: "unknown", sessionId: session.id, ...workspaceId !== undefined ? { workspaceId } : {}, lastSeenAt: session.updatedAt } };
93336
+ const slateRead = await readSlateState(dir);
93337
+ const workspaceId = slateRead.state === "ok" ? slateRead.slate.workspaceId : undefined;
93338
+ return {
93339
+ kind: "unknown",
93340
+ item: {
93341
+ type: "unknown",
93342
+ sessionId: session.id,
93343
+ ...workspaceId !== undefined ? { workspaceId } : {},
93344
+ lastSeenAt: session.updatedAt,
93345
+ reason: slateRead.state === "unreadable" ? "slate-unreadable" : "no-resolution-recorded"
93346
+ }
93347
+ };
93284
93348
  }
93285
93349
  async function readExternalUnboundCandidates(cwd) {
93286
93350
  const extDir = externalSlatesDir(cwd);
@@ -93533,6 +93597,17 @@ async function isSlateEngaged(dir) {
93533
93597
  return false;
93534
93598
  }
93535
93599
  }
93600
+ async function readSlateState(dir) {
93601
+ try {
93602
+ const slate = await readSlate(dir);
93603
+ return slate === undefined ? { state: "absent" } : { state: "ok", slate };
93604
+ } catch (error) {
93605
+ return isNotFound2(error) ? { state: "absent" } : { state: "unreadable" };
93606
+ }
93607
+ }
93608
+ function isNotFound2(error) {
93609
+ return typeof error === "object" && error !== null && error.code === "ENOENT";
93610
+ }
93536
93611
  async function safeReadSlate(dir) {
93537
93612
  try {
93538
93613
  return await readSlate(dir);
@@ -94498,7 +94573,7 @@ function summarizeReviewItem(item) {
94498
94573
  case "unbound-candidate":
94499
94574
  return `${item.sessionId} \u2014 ${item.summary}`;
94500
94575
  case "unknown":
94501
- return `${item.sessionId} \u2014 last seen ${item.lastSeenAt}`;
94576
+ return `${item.sessionId} \u2014 ${unknownReasonLabel(item.reason)} (last seen ${item.lastSeenAt})`;
94502
94577
  }
94503
94578
  }
94504
94579
  function formatReviewListLines(items, selected) {
@@ -94510,6 +94585,26 @@ function formatReviewListLines(items, selected) {
94510
94585
  return `${mark} ${TYPE_LABEL[item.type].padEnd(8)} ${summarizeReviewItem(item)}`;
94511
94586
  });
94512
94587
  }
94588
+ function unknownReasonLabel(reason) {
94589
+ switch (reason) {
94590
+ case "wrap-up-failed":
94591
+ return "wrap-up failed";
94592
+ case "slate-unreadable":
94593
+ return "slate.json unreadable";
94594
+ case "no-resolution-recorded":
94595
+ return "no resolution recorded";
94596
+ }
94597
+ }
94598
+ function unknownReasonDetail(reason) {
94599
+ switch (reason) {
94600
+ case "wrap-up-failed":
94601
+ return "a wrap-up dispatch ran and every group failed - the per-kind reasons are above where recorded; re-run wrap-up after fixing them.";
94602
+ case "slate-unreadable":
94603
+ return "this session's slate.json exists but could not be read. Its record is DAMAGED, not absent: inspect the file before assuming nothing happened.";
94604
+ case "no-resolution-recorded":
94605
+ return "Slate engagement with no proposal, terminal state, unbound-candidate or wrap-up-outcome artifact recorded.";
94606
+ }
94607
+ }
94513
94608
  function describeReviewItem(item) {
94514
94609
  switch (item.type) {
94515
94610
  case "proposal":
@@ -94551,7 +94646,7 @@ function describeReviewItem(item) {
94551
94646
  ] : [
94552
94647
  `Session ${item.sessionId}${item.workspaceId !== undefined ? ` (workspace ${item.workspaceId})` : ""}`,
94553
94648
  `Last seen ${item.lastSeenAt}`,
94554
- "No proposal, terminal state, or unbound-candidate artifact recorded.",
94649
+ "Why unknown: " + unknownReasonDetail(item.reason),
94555
94650
  "",
94556
94651
  `Investigate: keryx sessions list / keryx shell -r ${item.sessionId}`
94557
94652
  ];
@@ -115928,7 +116023,8 @@ async function workspaceCommand(args) {
115928
116023
  if (subcommand === "create") {
115929
116024
  rejectUnknownOptions(args.slice(1), new Set(["--title", "--component"]));
115930
116025
  const title = optionValue(args, "--title");
115931
- const component = optionValue(args, "--component");
116026
+ const rawComponent = optionValue(args, "--component");
116027
+ const component = rawComponent === undefined ? undefined : requireWorkspaceReference(rawComponent);
115932
116028
  if (!title)
115933
116029
  throw new Error("Usage: keryx workspace create --title <title> [--component <workspace-relative-ref>]");
115934
116030
  const workspace = await service6().create({ request: undefined, requestCorrelationId: randomUUID40(), id: newWorkspaceId(), title, ...component ? { component: { kind: "component", uri: component } } : {} });
@@ -115957,11 +116053,11 @@ async function workspaceCommand(args) {
115957
116053
  rejectUnknownOptions(args.slice(2), new Set(["--kind", "--uri", "--revision"]));
115958
116054
  const workspaceId = args[1];
115959
116055
  const kind = optionValue(args, "--kind");
115960
- const uri = optionValue(args, "--uri");
116056
+ const rawUri = optionValue(args, "--uri");
115961
116057
  const revision = optionValue(args, "--revision");
115962
- if (!workspaceId || !kind || !uri)
116058
+ if (!workspaceId || !kind || !rawUri)
115963
116059
  throw new Error("Usage: keryx workspace add-resource <workspace-id> --kind <kind> --uri <workspace-relative-ref> [--revision <revision>]");
115964
- console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID40(), workspaceId, resource: { kind, uri, ...revision ? { revision } : {} } }), null, 2));
116060
+ console.log(JSON.stringify(await service6().addResource({ request: undefined, requestCorrelationId: randomUUID40(), workspaceId, resource: { kind, uri: requireWorkspaceReference(rawUri), ...revision ? { revision } : {} } }), null, 2));
115965
116061
  return;
115966
116062
  }
115967
116063
  if (subcommand === "archive") {
@@ -116235,7 +116331,7 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
116235
116331
  sections.push(renderSection("Pending proposals", report.proposals, (item) => `- Accept, reject, or dismiss ${item.kind} proposal ${item.proposalId} in workspace ${item.workspaceId}` + `${item.note !== undefined ? `: "${item.note}"` : ""}? ` + `Recommendation: ${item.fresh ? "evidence is fresh \u2014 review now (`keryx workspace review " + item.workspaceId + " " + item.proposalId + " --decision <accepted|rejected|dismissed>`)" : "evidence has drifted since this proposal was created \u2014 treat as stale, re-run wrap-up before deciding"}.`));
116236
116332
  sections.push(renderSection("Blocked sessions (stopped unattended)", report.blocked, (item) => `- Session ${item.sessionId} stopped unattended (${item.terminalState.reason}) at ${item.terminalState.occurredAt}. Resume it, or archive and move on? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to resume and unblock it.`));
116237
116333
  sections.push(renderSection("Unbound candidates (wrap-up ran, no workspace bound)", report.unboundCandidates, (item) => `- Session ${item.sessionId} produced untriaged seeds with no workspace bound (${item.summary}). Bind to a workspace and propose, or discard? ` + `Recommendation: pick a workspace, then \`keryx workspace propose <workspace-id> --kind <kind> --session ${item.sessionId}\` (evidence: ${item.evidencePath}).`));
116238
- sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt} with no proposal, terminal state, or unbound-candidate artifact recorded. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
116334
+ sections.push(renderSection("Unknown (no resolution recorded)", report.unknown, (item) => `- Session ${item.sessionId} was last seen ${item.lastSeenAt}: ${describeUnknownReason(item)}. Investigate, or ignore? ` + `Recommendation: \`keryx sessions list\` / \`keryx shell -r ${item.sessionId}\` to see what happened.`));
116239
116335
  sections.push(renderSection("Unreviewed SAC-owned changes (no proposal on record)", report.unreviewedPaths, (item) => `- Session ${item.sessionId} changed ${item.owner} path \`${item.path}\`${item.status !== undefined ? ` (Status: ${item.status})` : ""} at ${item.changedAt} with NO SAC proposal/receipt behind it \u2014 this looks like it bypassed review. Was this reviewed some other way, or should it be? ` + `Recommendation: \`keryx shell -r ${item.sessionId}\` to see what happened; if the content is good, route it through a real proposal (\`keryx workspace propose ...\`) before trusting it as durable knowledge.`));
116240
116336
  if (includeLifecycleFlags) {
116241
116337
  sections.push(renderSection("Lifecycle flags (component no longer in the graph)", report.lifecycleFlags, (item) => `- ${item.kind} \`${item.ref}\` scopes to \`${item.missingComponent}\`, which is no longer in the code graph (flagged ${item.flaggedAt}). Still relevant, or safe to clean up? ` + `Recommendation: this is report-only \u2014 nothing was archived/edited/removed automatically; ${item.kind === "workspace" ? "`keryx workspace archive " + item.ref + "`" : item.kind === "memory-entry" ? "`keryx memory supersede` or edit the entry directly" : "edit or remove the wiki page directly"} if you decide it's actually stale.`));
@@ -116244,6 +116340,16 @@ function renderCatchUp(report, includeLifecycleFlags = true) {
116244
116340
 
116245
116341
  `);
116246
116342
  }
116343
+ function describeUnknownReason(item) {
116344
+ switch (item.reason) {
116345
+ case "wrap-up-failed":
116346
+ return "a wrap-up dispatch ran and every group failed (re-run it after fixing credentials/conflicts)";
116347
+ case "slate-unreadable":
116348
+ return "its slate.json is present but could not be read - the session's own record is damaged, not merely unrecorded";
116349
+ case "no-resolution-recorded":
116350
+ return "it has Slate engagement but no proposal, terminal state, unbound-candidate or wrap-up-outcome artifact";
116351
+ }
116352
+ }
116247
116353
  function renderSection(title, items, describe) {
116248
116354
  const lines = [`== ${title} ==`];
116249
116355
  if (items.length === 0)
package/dist/core.js CHANGED
@@ -20387,10 +20387,12 @@ __export(exports_service9, {
20387
20387
  normalizeCollaborationResult: () => normalizeCollaborationResult,
20388
20388
  normalizeFwkResult: () => normalizeFwkResult,
20389
20389
  normalizeProposalLifecycleResult: () => normalizeProposalLifecycleResult,
20390
+ normalizeWorkspaceReference: () => normalizeWorkspaceReference,
20390
20391
  proposalNotePath: () => proposalNotePath,
20391
20392
  readExternalSlate: () => readExternalSlate,
20392
20393
  reclaimStaleExternalSlates: () => reclaimStaleExternalSlates,
20393
20394
  redactSensitiveText: () => redactSensitiveText,
20395
+ requireWorkspaceReference: () => requireWorkspaceReference,
20394
20396
  resolveOrCreateWorkspace: () => resolveOrCreateWorkspace,
20395
20397
  resolvePolicyExperiment: () => resolvePolicyExperiment,
20396
20398
  rollbackPolicyExperiment: () => rollbackPolicyExperiment,
@@ -20401,8 +20403,50 @@ __export(exports_service9, {
20401
20403
  });
20402
20404
 
20403
20405
  // src/sac/fwk-service.ts
20404
- init_assembly();
20405
20406
  import { createHash as createHash15, randomUUID as randomUUID4 } from "crypto";
20407
+
20408
+ // src/sac/workspace-reference.ts
20409
+ var WORKSPACE_REFERENCE_PATTERN = /^\.\/(?!(?:.*\/)?\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
20410
+ var UNSAFE_ABSOLUTE = /^[/\\]/;
20411
+ var UNSAFE_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
20412
+ function requireWorkspaceReference(raw) {
20413
+ const normalized = normalizeWorkspaceReference(raw);
20414
+ if (!normalized.ok)
20415
+ throw new Error(normalized.reason);
20416
+ return normalized.uri;
20417
+ }
20418
+ function normalizeWorkspaceReference(raw) {
20419
+ const trimmed = raw.trim();
20420
+ if (trimmed.length === 0) {
20421
+ return { ok: false, reason: 'a workspace reference must be a non-empty workspace-relative path, e.g. "./src/harness/search"' };
20422
+ }
20423
+ if (UNSAFE_ABSOLUTE.test(trimmed) || UNSAFE_SCHEME.test(trimmed) || trimmed.includes("\\")) {
20424
+ return {
20425
+ ok: false,
20426
+ reason: `"${trimmed}" is not a workspace-relative path \u2014 an absolute path, a URL and a Windows path are all refused. Use a path inside the project, e.g. "./src/harness/search".`
20427
+ };
20428
+ }
20429
+ const withoutDotSlash = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
20430
+ const withoutTrailingSlash = withoutDotSlash.replace(/\/+$/, "");
20431
+ if (withoutTrailingSlash.length === 0) {
20432
+ return { ok: false, reason: `"${trimmed}" names the project root rather than a path inside it \u2014 name a directory or file, e.g. "./src/harness/search".` };
20433
+ }
20434
+ const segments = withoutTrailingSlash.split("/");
20435
+ if (segments.includes("..") || segments.includes(".")) {
20436
+ return { ok: false, reason: `"${trimmed}" contains a "." or ".." path segment \u2014 a workspace reference must stay inside the project.` };
20437
+ }
20438
+ const uri = `./${withoutTrailingSlash}`;
20439
+ if (!WORKSPACE_REFERENCE_PATTERN.test(uri)) {
20440
+ return {
20441
+ ok: false,
20442
+ reason: `"${trimmed}" is not a usable workspace reference. The stored form is "${uri}" if the characters are the problem: ` + 'each path segment may contain only letters, digits, ".", "_" and "-", and no ".." segment is allowed.'
20443
+ };
20444
+ }
20445
+ return { ok: true, uri };
20446
+ }
20447
+
20448
+ // src/sac/fwk-service.ts
20449
+ init_assembly();
20406
20450
  import { appendFile as appendFile5, mkdir as mkdir28, open as open2, readFile as readFile40, rename as rename6, rm as rm5, stat as stat9, writeFile as writeFile21 } from "fs/promises";
20407
20451
  import path78 from "path";
20408
20452
 
@@ -20415,7 +20459,7 @@ var idPattern = /^[a-z][a-z0-9-]{2,63}$/;
20415
20459
  var subjectPattern = /^(?:user|team|service|agent):[a-z0-9][a-z0-9._-]{0,127}$/;
20416
20460
  var revisionPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
20417
20461
  var correlationPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{15,255}$/;
20418
- var workspacePathPattern = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
20462
+ var workspacePathPattern = WORKSPACE_REFERENCE_PATTERN;
20419
20463
  var receiptHashPattern = /^[a-f0-9]{64}$/;
20420
20464
  var utcPattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?Z$/;
20421
20465
  var forbiddenPayloadKeys = new Set(["prompt", "transcript", "hiddenReasoning", "secret", "secrets", "rawContent"]);
@@ -21661,7 +21705,7 @@ var stableJson2 = (value) => JSON.stringify(stableValue(value));
21661
21705
  var sha2562 = (value) => createHash14("sha256").update(stableJson2(value), "utf8").digest("hex");
21662
21706
  var sha256Content = (value) => createHash14("sha256").update(value).digest("hex");
21663
21707
  var pseudonym = (key, value) => createHmac2("sha256", key).update(value, "utf8").digest("hex");
21664
- var workspacePathPattern2 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
21708
+ var workspacePathPattern2 = WORKSPACE_REFERENCE_PATTERN;
21665
21709
  var trustedOutcomes = new WeakMap;
21666
21710
  var trustedSandboxEvidence = new WeakMap;
21667
21711
  var trustedSandboxExecutions = new WeakMap;
@@ -22138,7 +22182,7 @@ var metadataOnly = (value) => {
22138
22182
  var nowIso = (now) => now().toISOString();
22139
22183
  var reportHashPattern = /^[a-f0-9]{64}$/;
22140
22184
  var immutableVersionPattern = /(?:^|[-_.:])(?:latest|next|head|main|develop)(?:$|[-_.:])/i;
22141
- var workspacePathPattern3 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
22185
+ var workspacePathPattern3 = WORKSPACE_REFERENCE_PATTERN;
22142
22186
  var policyExperimentConfigPath = [".metaproject", "context-operations", "policy-experiment", "config.json"];
22143
22187
  var stableValue2 = (value) => {
22144
22188
  if (Array.isArray(value))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.130",
3
+ "version": "0.2.131",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {