@brainervirus/workit-cursor 1.0.12 → 1.1.0

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.
@@ -6625,7 +6625,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
6625
6625
  import path15 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-mcp/src/server.ts
6628
- import { existsSync as existsSync7, readFileSync as readFileSync7, realpathSync as realpathSync7 } from "node:fs";
6628
+ import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync7 } from "node:fs";
6629
6629
  import path14 from "node:path";
6630
6630
 
6631
6631
  // node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
@@ -20052,7 +20052,13 @@ var refSchema = discriminatedUnion2("kind", [
20052
20052
  id
20053
20053
  }).strict(),
20054
20054
  object4({ kind: literal3("host"), host: hostSchema, handle: nonEmpty }).strict(),
20055
- object4({ kind: literal3("external"), url: text.url() }).strict()
20055
+ object4({ kind: literal3("external"), url: text.url() }).strict(),
20056
+ object4({
20057
+ kind: literal3("standing"),
20058
+ workspace: nonEmpty,
20059
+ class: nonEmpty,
20060
+ configDigest: nullableDigest
20061
+ }).strict()
20056
20062
  ]);
20057
20063
  var rewriteUnknown = (value, map) => {
20058
20064
  if (Array.isArray(value)) {
@@ -20357,7 +20363,8 @@ var decisionSchema = object4({
20357
20363
  approvedContent: text,
20358
20364
  displayed: text.optional(),
20359
20365
  contentRefs: array3(refSchema),
20360
- statedChoice: object4({ ref: text, text }).strict().optional()
20366
+ statedChoice: object4({ ref: text, text }).strict().optional(),
20367
+ standing: object4({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
20361
20368
  }).strict(),
20362
20369
  digest,
20363
20370
  response: _enum2(["approved", "rejected", "stated"]),
@@ -22433,1622 +22440,1784 @@ function assertProductWriteAllowed(input) {
22433
22440
  }
22434
22441
 
22435
22442
  // packages/workit-core/src/core/task-evaluation.ts
22443
+ import { createHash as createHash5 } from "node:crypto";
22444
+ import * as fs7 from "node:fs";
22445
+ import { spawnSync as spawnSync2 } from "node:child_process";
22446
+ import path9 from "node:path";
22447
+
22448
+ // packages/workit-core/src/core/authority.ts
22436
22449
  import { createHash as createHash4 } from "node:crypto";
22437
22450
  import * as fs6 from "node:fs";
22438
- import { spawnSync } from "node:child_process";
22439
- import path6 from "node:path";
22451
+ import path8 from "node:path";
22440
22452
 
22441
- // packages/workit-core/src/core/authority.ts
22453
+ // packages/workit-core/src/core/auto-approval.ts
22442
22454
  import { createHash as createHash3 } from "node:crypto";
22443
- import * as fs5 from "node:fs";
22455
+ import { readFileSync as readFileSync4 } from "node:fs";
22456
+
22457
+ // packages/workit-core/src/core/workspaces.ts
22458
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
22459
+ import path6 from "node:path";
22460
+
22461
+ // packages/workit-core/src/core/config.ts
22462
+ import {
22463
+ copyFileSync,
22464
+ cpSync,
22465
+ existsSync as existsSync3,
22466
+ mkdirSync as mkdirSync2,
22467
+ readFileSync as readFileSync2,
22468
+ readdirSync as readdirSync2,
22469
+ writeFileSync as writeFileSync2
22470
+ } from "node:fs";
22471
+ import os from "node:os";
22444
22472
  import path5 from "node:path";
22445
- var verifiedAuthorities = new WeakMap;
22446
- var trustedAuthority = (kind, provenance, expected, binding, caller) => {
22447
- const token = {};
22448
- verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
22449
- return token;
22473
+
22474
+ // packages/workit-core/src/core/boundary.ts
22475
+ import { statSync } from "node:fs";
22476
+ var EVENT = {
22477
+ initialization: "initialization",
22478
+ provenance: "provenance",
22479
+ assets: "assets",
22480
+ configurationSource: "configuration_source",
22481
+ hooks: "hooks",
22482
+ mcpConnection: "mcp_connection",
22483
+ migration: "migration",
22484
+ installSteps: "install_steps",
22485
+ uncaughtFailure: "uncaught_failure",
22486
+ toolsFailed: "tools_failed",
22487
+ doctor: "doctor"
22450
22488
  };
22451
- var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
22452
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22453
- var normalizeChainSteps = (steps) => {
22454
- if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
22455
- return null;
22456
- const normalized = [];
22457
- for (const step of steps) {
22458
- if (typeof step === "string") {
22459
- if (!step.trim())
22460
- return null;
22461
- normalized.push({ kind: "commit", message: step });
22462
- continue;
22463
- }
22464
- if (!isRecord(step))
22465
- return null;
22466
- if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
22467
- normalized.push({ kind: "branch", target: step.branch });
22468
- continue;
22469
- }
22470
- if (step.pr === true && Object.keys(step).length === 1) {
22471
- normalized.push({ kind: "pr" });
22489
+ var errorDetail = (err) => {
22490
+ if (err instanceof Error) {
22491
+ return { error_name: err.name, error: err.message };
22492
+ }
22493
+ return { error_name: "unknown", error: String(err) };
22494
+ };
22495
+ var markSourcesLoaded = (files, loadedAt = Date.now()) => ({
22496
+ loadedAt,
22497
+ files: [...files]
22498
+ });
22499
+ var changedSourcesSinceLoad = (marker) => {
22500
+ const changed = [];
22501
+ for (const file of marker.files) {
22502
+ try {
22503
+ if (statSync(file).mtimeMs > marker.loadedAt)
22504
+ changed.push(file);
22505
+ } catch {}
22506
+ }
22507
+ return changed;
22508
+ };
22509
+
22510
+ // packages/workit-core/src/core/config.ts
22511
+ var diagnosticLogger;
22512
+ var PRESETS = {
22513
+ gitflow: {
22514
+ allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
22515
+ protected: ["main", "develop", "master", "prod", "production"]
22516
+ },
22517
+ "github-flow": { allowed: ["*"], protected: ["main"] },
22518
+ "trunk-based": { allowed: ["*"], protected: ["main"] },
22519
+ custom: { allowed: [], protected: [] }
22520
+ };
22521
+ var mergePreset = (preset, input = {}, current = {
22522
+ branchPolicy: { preset, allowed: [], protected: [] }
22523
+ }) => {
22524
+ const defs = PRESETS[preset];
22525
+ return {
22526
+ preset,
22527
+ allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
22528
+ protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
22529
+ };
22530
+ };
22531
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workit");
22532
+ var migratedDir = null;
22533
+ var migrationFailed = false;
22534
+ var ensureConfigDir = (dir = resolveConfigDir()) => {
22535
+ if (migratedDir === dir)
22536
+ return dir;
22537
+ if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
22538
+ migratedDir = dir;
22539
+ return dir;
22540
+ }
22541
+ const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
22542
+ if (!existsSync3(legacy)) {
22543
+ migratedDir = dir;
22544
+ return dir;
22545
+ }
22546
+ if (!migrationFailed && existsSync3(dir)) {
22547
+ migratedDir = dir;
22548
+ return dir;
22549
+ }
22550
+ migrationFailed = false;
22551
+ mkdirSync2(dir, { recursive: true });
22552
+ diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
22553
+ for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
22554
+ const src = path5.join(legacy, entry.name);
22555
+ const dest = path5.join(dir, entry.name);
22556
+ if (existsSync3(dest))
22472
22557
  continue;
22558
+ try {
22559
+ if (entry.isDirectory())
22560
+ cpSync(src, dest, { recursive: true });
22561
+ else if (entry.isFile())
22562
+ copyFileSync(src, dest);
22563
+ } catch (err) {
22564
+ migrationFailed = true;
22565
+ diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
22566
+ console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
22473
22567
  }
22474
- return null;
22475
22568
  }
22476
- const keys = normalized.map(chainStepKey);
22477
- if (new Set(keys).size !== keys.length)
22569
+ if (!migrationFailed)
22570
+ migratedDir = dir;
22571
+ return dir;
22572
+ };
22573
+ var configDir = () => ensureConfigDir();
22574
+ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
22575
+ var DEFAULTS = {
22576
+ locale: "en",
22577
+ localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
22578
+ timezone: "America/Santiago",
22579
+ branchPolicy: {
22580
+ preset: "gitflow",
22581
+ allowed: [...PRESETS.gitflow.allowed],
22582
+ protected: [...PRESETS.gitflow.protected]
22583
+ },
22584
+ commitPolicy: { preset: "conventional" }
22585
+ };
22586
+ var readSafe = (p) => {
22587
+ try {
22588
+ return readFileSync2(p, "utf8");
22589
+ } catch {
22478
22590
  return null;
22479
- return normalized;
22591
+ }
22480
22592
  };
22481
- var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
22482
- var parseSteps = (content) => {
22483
- let value;
22593
+ var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
22594
+ var parseConfigResult = (raw, file) => {
22595
+ if (raw === null)
22596
+ return { status: "missing", path: file };
22597
+ let parsed;
22484
22598
  try {
22485
- value = JSON.parse(content);
22599
+ parsed = JSON.parse(raw);
22486
22600
  } catch {
22487
- return [];
22601
+ return { status: "malformed", path: file, error: `${file} is not valid JSON` };
22488
22602
  }
22489
- if (typeof value !== "object" || value === null || !("steps" in value))
22490
- return [];
22491
- const steps = value.steps;
22492
- if (!Array.isArray(steps))
22493
- return null;
22494
- const normalized = normalizeChainSteps(steps);
22495
- return normalized;
22603
+ if (!isConfigObject(parsed)) {
22604
+ return { status: "malformed", path: file, error: `${file} is not a JSON object` };
22605
+ }
22606
+ const input = parsed;
22607
+ const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
22608
+ const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
22609
+ const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
22610
+ return {
22611
+ status: "valid",
22612
+ path: file,
22613
+ config: {
22614
+ locale,
22615
+ localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
22616
+ timezone: input.timezone ?? DEFAULTS.timezone,
22617
+ branchPolicy: mergePreset(preset, {
22618
+ allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
22619
+ protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
22620
+ }, DEFAULTS),
22621
+ commitPolicy: {
22622
+ preset: commitPreset,
22623
+ ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
22624
+ }
22625
+ }
22626
+ };
22496
22627
  };
22497
- var decisionMatches = (entry, purpose, binding) => {
22498
- const decision = entry.data;
22499
- return binding.taskId === decision.binding.taskId && binding.workspaceId === decision.binding.workspaceId && decision.purpose === purpose && decision.response === "approved" && decision.revoked === null && (purpose !== "action" || decision.consumption === null) && decision.digest === decisionDigest(decision) && scopeEqual(decision.binding.scope, binding.scope) && canonicalJson(decision.binding) === canonicalJson(binding);
22628
+ var readConfigTyped = (dir) => {
22629
+ const file = path5.join(dir ?? configDir(), "config.json");
22630
+ return parseConfigResult(readSafe(file), file);
22500
22631
  };
22501
- var sameRef = (left, right) => canonicalJson(left) === canonicalJson(right);
22502
- var validProvenance = (value, caller) => {
22503
- if (!provenanceSchema.safeParse(value).success)
22504
- return false;
22505
- const provenance = value;
22506
- return provenance.kind === "host_observed" && provenance.host === caller.host && provenance.session?.kind === "host" && provenance.session.host === caller.host && provenance.session.handle === caller.actor && provenance.receipts.some((receipt) => receipt.kind === "host" && receipt.host === caller.host);
22632
+ var readConfig = () => {
22633
+ const result = readConfigTyped();
22634
+ if (result.status === "malformed")
22635
+ throw new Error(result.error);
22636
+ return result.config ?? DEFAULTS;
22507
22637
  };
22508
- var verifyNativeDecision = (verifier, input, binding) => {
22509
- if (!verifier)
22510
- return failure("permission_denied", "native authority verifier is unavailable");
22511
- const result = verifier.verifyDecision(input);
22512
- if (!result.ok || !validProvenance(result.data, input.caller))
22513
- return failure("permission_denied", "native decision observation was not attested");
22514
- return success(null, null, trustedAuthority("decision", result.data, {
22515
- taskId: input.expected.taskId,
22516
- workspaceId: input.expected.workspaceId,
22517
- purpose: input.expected.purpose,
22518
- response: input.expected.response,
22519
- binding: input.expected.binding,
22520
- bindingBytes: input.expected.bindingBytes,
22521
- digest: input.expected.digest,
22522
- requirementIds: [...input.expected.requirementIds]
22523
- }, binding, input.caller));
22638
+ var resolveBranchPolicy = (config, workspace) => {
22639
+ const wp = workspace?.branchPolicy ?? {};
22640
+ const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
22641
+ const merged = mergePreset(preset, {
22642
+ allowed: wp.allowed,
22643
+ protectedNames: wp.protected
22644
+ }, config);
22645
+ const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
22646
+ return {
22647
+ preset,
22648
+ allowed,
22649
+ protected: new Set(merged.protected.map((p) => p.toLowerCase())),
22650
+ integration: wp.integration === "merge" ? "merge" : "pr",
22651
+ defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop"
22652
+ };
22524
22653
  };
22525
- var verifyNativeAction = (verifier, input, binding) => {
22526
- if (!verifier)
22527
- return failure("permission_denied", "native authority verifier is unavailable");
22528
- const result = verifier.verifyAction(input);
22529
- if (!result.ok || !validProvenance(result.data, input.caller))
22530
- return failure("permission_denied", "native action observation was not attested");
22531
- return success(null, null, trustedAuthority("action", result.data, {
22532
- taskId: input.expected.taskId,
22533
- workspaceId: input.expected.workspaceId,
22534
- decisionId: input.expected.decisionId,
22535
- actionRef: input.expected.actionRef,
22536
- taskRevision: input.expected.taskRevision,
22537
- workspaceRevision: input.expected.workspaceRevision,
22538
- outcome: input.expected.outcome,
22539
- purpose: input.expected.decision.purpose,
22540
- response: input.expected.decision.response,
22541
- binding: input.expected.decision.binding,
22542
- digest: input.expected.decision.digest,
22543
- requirementIds: [...input.expected.decision.requirementIds]
22544
- }, binding, input.caller));
22545
- };
22546
- var verifyNativeReconciliation = (verifier, input, binding) => {
22547
- if (!verifier?.verifyReconciliation)
22548
- return failure("permission_denied", "native reconciliation authority is unavailable");
22549
- const result = verifier.verifyReconciliation(input);
22550
- if (!result.ok || !validProvenance(result.data, input.caller))
22551
- return failure("permission_denied", "native reconciliation observation was not attested");
22552
- return success(null, null, trustedAuthority("reconciliation", result.data, {
22553
- taskId: input.expected.taskId,
22554
- workspaceId: input.expected.workspaceId,
22555
- decisionId: input.expected.decisionId,
22556
- actionRef: input.expected.actionRef,
22557
- taskRevision: input.expected.taskRevision,
22558
- workspaceRevision: input.expected.workspaceRevision,
22559
- outcome: input.expected.outcome,
22560
- evidenceDigest: input.expected.evidenceDigest,
22561
- step: input.expected.step,
22562
- purpose: input.expected.decision.purpose,
22563
- response: input.expected.decision.response,
22564
- binding: input.expected.decision.binding,
22565
- digest: input.expected.decision.digest,
22566
- requirementIds: [...input.expected.decision.requirementIds]
22567
- }, binding, input.caller));
22568
- };
22569
- var takeAuthority = (authority, binding, caller) => {
22570
- const record = verifiedAuthorities.get(authority);
22571
- verifiedAuthorities.delete(authority);
22572
- if (!record)
22573
- return null;
22574
- if (record.owner !== binding.owner || record.store !== binding.store || record.root !== binding.root || record.root !== record.store.root || canonicalJson(record.caller) !== canonicalJson(caller))
22575
- return null;
22576
- return record;
22654
+ var COMMIT_PRESETS = [
22655
+ "conventional",
22656
+ "gitmoji",
22657
+ "ticket-prefix",
22658
+ "freeform",
22659
+ "custom",
22660
+ "auto"
22661
+ ];
22662
+
22663
+ // packages/workit-core/src/core/workspaces.ts
22664
+ var readWorkspacesResult = (dir = configDir()) => {
22665
+ const file = path6.join(dir, "workspaces.json");
22666
+ let raw;
22667
+ try {
22668
+ raw = readFileSync3(file, "utf8");
22669
+ } catch {
22670
+ return { status: "missing", path: file, entries: [] };
22671
+ }
22672
+ let parsed;
22673
+ try {
22674
+ parsed = JSON.parse(raw);
22675
+ } catch {
22676
+ return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
22677
+ }
22678
+ if (!isConfigObject(parsed)) {
22679
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
22680
+ }
22681
+ const list = parsed.workspaces;
22682
+ return {
22683
+ status: "valid",
22684
+ path: file,
22685
+ entries: Array.isArray(list) ? list : []
22686
+ };
22577
22687
  };
22578
- var retireNativeAuthority = (authority) => {
22579
- const record = verifiedAuthorities.get(authority);
22580
- verifiedAuthorities.delete(authority);
22581
- return record?.provenance ?? null;
22688
+ var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
22689
+ var globToRegExp = (glob) => {
22690
+ let out = "";
22691
+ for (let i = 0;i < glob.length; i++) {
22692
+ const c = glob[i];
22693
+ if (c === "*") {
22694
+ if (glob[i + 1] === "*") {
22695
+ if (glob[i + 2] === "/") {
22696
+ if (out === "")
22697
+ out += "/?";
22698
+ out += "(?:[^/]+/)*";
22699
+ i += 2;
22700
+ } else {
22701
+ if (i + 2 >= glob.length) {
22702
+ if (out.endsWith("/")) {
22703
+ out = out.slice(0, -1);
22704
+ out += "(?:/.*)?";
22705
+ } else {
22706
+ out += ".*";
22707
+ }
22708
+ } else {
22709
+ out += ".*";
22710
+ }
22711
+ i++;
22712
+ }
22713
+ } else {
22714
+ out += "[^/]*";
22715
+ }
22716
+ } else {
22717
+ out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
22718
+ }
22719
+ }
22720
+ return new RegExp(`^${out}$`);
22582
22721
  };
22583
- var authorityMatches = (authority, expected) => authority.kind === "action" && authority.taskId === expected.taskId && authority.workspaceId === expected.workspaceId && authority.decisionId === expected.decisionId && sameRef(authority.actionRef, expected.actionRef) && authority.taskRevision === expected.taskRevision && authority.workspaceRevision === expected.workspaceRevision && authority.outcome === expected.outcome;
22584
- var authorityDecisionMatches = (authority, decision) => authority.purpose === decision.purpose && authority.response === decision.response && authority.digest === decision.digest && canonicalJson(authority.binding) === canonicalJson(decision.binding) && canonicalJson(authority.requirementIds) === canonicalJson(decision.requirementIds);
22585
- var provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session);
22586
- function applicableDecision(task, purpose, binding, checkoutRoot) {
22587
- if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
22588
- return [];
22589
- return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
22590
- }
22591
- var digestBytes2 = (bytes) => createHash3("sha256").update(bytes).digest("hex");
22592
- var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
22593
- let root;
22722
+ var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
22723
+ var realpathOf = (p) => {
22724
+ const real = realpathSync2.native ?? realpathSync2;
22594
22725
  try {
22595
- root = fs5.realpathSync(checkoutRoot);
22726
+ return real(p);
22596
22727
  } catch {
22597
- return failure("permission_denied", "checkout root is unavailable");
22728
+ return p;
22598
22729
  }
22599
- for (const reference of binding.contentRefs) {
22600
- if (reference.kind !== "file")
22730
+ };
22731
+ var canonicalGlob = (glob) => {
22732
+ const m = /[*?[\]{]/.exec(glob);
22733
+ const prefix = m ? glob.slice(0, m.index) : glob;
22734
+ const rest = m ? glob.slice(m.index) : "";
22735
+ if (!prefix)
22736
+ return glob;
22737
+ const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
22738
+ return real + (prefix.endsWith("/") ? "/" : "") + rest;
22739
+ };
22740
+ var resolveWorkspaceFrom = (cwd, dir) => {
22741
+ const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
22742
+ for (const entry of loadWorkspacesFrom(dir)) {
22743
+ if (!entry || typeof entry !== "object")
22601
22744
  continue;
22602
- if (!reference.digest)
22603
- return failure("invalid_input", "document references require a byte digest");
22604
- const target = path5.resolve(root, reference.path);
22605
- if (target !== root && !target.startsWith(`${root}${path5.sep}`))
22606
- return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22607
- fields: [
22608
- { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
22609
- ]
22610
- });
22611
- try {
22612
- const relative = path5.relative(root, target);
22613
- let current = root;
22614
- for (const segment of relative.split(path5.sep)) {
22615
- if (!segment)
22616
- continue;
22617
- current = path5.join(current, segment);
22618
- const stat = fs5.lstatSync(current);
22619
- const real = fs5.realpathSync(current);
22620
- if (real !== root && !real.startsWith(`${root}${path5.sep}`))
22621
- return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22622
- fields: [
22623
- {
22624
- path: "contentRefs.path",
22625
- reason: `${reference.path} resolves outside the checkout`
22626
- }
22627
- ]
22628
- });
22629
- if (stat.isSymbolicLink())
22630
- return failure("invalid_input", "document reference uses a symlink");
22631
- if (current === target && !stat.isFile())
22632
- return failure("invalid_input", "document reference is not a regular file");
22633
- if (current !== target && !stat.isDirectory())
22634
- return failure("invalid_input", "document reference ancestor is not a directory");
22635
- }
22636
- if (digestBytes2(fs5.readFileSync(target)) !== reference.digest)
22637
- return failure("permission_denied", "approved document bytes have changed");
22638
- } catch {
22639
- return failure("invalid_input", "approved document is unavailable");
22745
+ const ws = entry;
22746
+ if (typeof ws.glob !== "string" || !ws.glob)
22747
+ continue;
22748
+ const glob = ws.glob.replaceAll("\\", "/");
22749
+ const canonical = canonicalGlob(glob);
22750
+ for (const target of targets) {
22751
+ if (matchWorkspace(glob, target))
22752
+ return ws;
22753
+ if (canonical !== glob && matchWorkspace(canonical, target))
22754
+ return ws;
22640
22755
  }
22641
22756
  }
22642
- return success(null, null, null);
22757
+ return null;
22643
22758
  };
22644
- var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
22645
- var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
22646
- var validateAction = (store, task, workspaceId, input, authority) => {
22647
- if (task.status !== "active")
22648
- return failure("invalid_transition", "only active tasks can authorize actions");
22649
- if (!refSchema.safeParse(input.actionRef).success)
22650
- return failure("invalid_input", "action reference is invalid");
22651
- const entry = task.decisions.find((candidate) => candidate.id === input.decisionId);
22652
- if (!entry)
22653
- return failure("not_found", "decision not found");
22654
- const decision = entry.data;
22655
- if (decision.purpose !== "action")
22656
- return failure("permission_denied", "decision is not an action approval");
22657
- if (!authorityMatches(authority, {
22658
- taskId: task.id,
22659
- workspaceId,
22660
- decisionId: input.decisionId,
22661
- actionRef: input.actionRef,
22662
- taskRevision: input.expectedRevision,
22663
- workspaceRevision: input.expectedWorkspaceRevision,
22664
- outcome: "reserve"
22665
- }))
22666
- return failure("permission_denied", "native reservation authority is not bound");
22667
- if (!authorityDecisionMatches(authority, decision))
22668
- return failure("permission_denied", "native reservation authority does not match decision");
22669
- if (decision.response !== "approved")
22670
- return failure("permission_denied", "decision was rejected");
22671
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
22672
- return failure("permission_denied", "action approval lacks native receipt assurance");
22673
- if (decision.revoked)
22674
- return failure("permission_denied", "decision is revoked");
22675
- if (decision.digest !== decisionDigest(decision))
22676
- return failure("permission_denied", "decision binding is invalid");
22677
- if (decision.binding.taskId !== task.id || decision.binding.workspaceId !== workspaceId)
22678
- return failure("permission_denied", "decision task or workspace binding is invalid");
22679
- if (input.binding && canonicalJson(input.binding) !== canonicalJson(decision.binding))
22680
- return failure("permission_denied", "action binding does not match the approved decision");
22681
- const content = verifyContentRefs(store, decision.binding);
22682
- if (!content.ok)
22683
- return content;
22684
- const requestedSteps = parseSteps(decision.binding.approvedContent);
22685
- if (requestedSteps === null)
22686
- return failure("invalid_input", "bounded action steps must be a unique non-empty array");
22687
- if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
22688
- return failure("permission_denied", "bounded action steps do not match approved content");
22689
- const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
22690
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
22691
- if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
22692
- return failure("permission_denied", "bounded action scope changed");
22693
- return success(null, null, { entry, workflow });
22759
+ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
22760
+
22761
+ // packages/workit-core/src/core/branch.ts
22762
+ import { execFileSync as execFileSync2 } from "node:child_process";
22763
+
22764
+ // packages/workit-core/src/core/git.ts
22765
+ import { execFileSync } from "node:child_process";
22766
+ var run = (cwd, args) => {
22767
+ try {
22768
+ const stdout = execFileSync("git", args, {
22769
+ cwd,
22770
+ encoding: "utf8",
22771
+ stdio: ["pipe", "pipe", "pipe"]
22772
+ });
22773
+ return { stdout: stdout.trimEnd(), stderr: "", exitCode: 0 };
22774
+ } catch (error) {
22775
+ const e = error;
22776
+ return {
22777
+ stdout: (e.stdout ?? "").trimEnd(),
22778
+ stderr: (e.stderr ?? "").trimEnd(),
22779
+ exitCode: e.status ?? 1
22780
+ };
22781
+ }
22694
22782
  };
22695
- function reserveAction(input) {
22696
- if (!input.store || !input.expectedRevision || !input.expectedWorkspaceRevision)
22697
- return failure("invalid_input", "action reservation preconditions are required");
22698
- const authority = takeAuthority(input.authority, {
22699
- owner: input.authorityOwner,
22700
- store: input.store,
22701
- root: input.store.root
22702
- }, input.authorityCaller);
22703
- if (!authority)
22704
- return failure("permission_denied", "native reservation authority is not verified");
22705
- const changed = input.store.mutateTask(input.taskId, input.expectedRevision, (current, mutation) => {
22706
- const workspace = input.store.readWorkspace();
22707
- if (!workspace.ok)
22708
- return workspace;
22709
- if (!workspace.data)
22710
- return failure("not_found", "workspace not found");
22711
- if (workspace.data.revision !== input.expectedWorkspaceRevision)
22712
- return failure("revision_conflict", "workspace revision does not match", {
22713
- expectedWorkspaceRevision: input.expectedWorkspaceRevision,
22714
- actualWorkspaceRevision: workspace.data.revision
22715
- });
22716
- const valid = validateAction(input.store, current, workspace.data.id, input, authority);
22717
- if (!valid.ok)
22718
- return valid;
22719
- const { entry, workflow } = valid.data;
22720
- const existing = entry.data.consumption;
22721
- if (existing?.state === "uncertain")
22722
- return failure("external_outcome_unknown", "previous action outcome is unknown", {
22723
- operation: "reserve_action",
22724
- outcome: "unknown"
22725
- });
22726
- if (existing?.state === "consumed")
22727
- return failure("permission_denied", "action approval is consumed");
22728
- if (existing?.state === "reserved" && workflow.completed.length === 0)
22729
- return failure("permission_denied", "action approval is already reserved");
22730
- const requestedStep = input.step ?? workflow.steps[workflow.completed.length];
22731
- if (workflow.steps.length && (!requestedStep || requestedStep !== workflow.steps[workflow.completed.length]))
22732
- return failure("permission_denied", "action step is outside the approved remaining workflow");
22733
- if (requestedStep && workflow.completed.includes(requestedStep))
22734
- return failure("permission_denied", "action step was already completed");
22735
- const nextDecision = {
22736
- ...entry.data,
22737
- consumption: { state: "reserved", at: mutation.now, actionRef: input.actionRef }
22738
- };
22739
- const actionProgress = workflow.steps.length ? [
22740
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
22741
- {
22742
- decisionId: input.decisionId,
22743
- steps: [...workflow.steps],
22744
- completedSteps: [...workflow.completed]
22745
- }
22746
- ] : current.actionProgress ?? [];
22747
- return success(mutation.revision, null, {
22748
- ...current,
22749
- actionProgress,
22750
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
22751
- });
22752
- }, input.native?.now);
22753
- if (!changed.ok)
22754
- return changed;
22755
- const workspace = input.store.readWorkspace();
22756
- if (!workspace.ok || !workspace.data)
22757
- return failure("recovery_required", "workspace disappeared after reservation");
22758
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
22759
- if (!entry)
22760
- return failure("recovery_required", "reserved decision disappeared");
22761
- const workflow = changed.data.actionProgress?.find((progress) => progress.decisionId === input.decisionId) ?? {
22762
- steps: [],
22763
- completedSteps: []
22764
- };
22765
- return success(changed.data.revision, workspace.data.revision, {
22766
- taskId: input.taskId,
22767
- decisionId: input.decisionId,
22768
- actionRef: input.actionRef,
22769
- taskRevision: changed.data.revision,
22770
- workspaceRevision: workspace.data.revision,
22771
- completedSteps: [...workflow.completedSteps],
22772
- remainingSteps: workflow.steps.slice(workflow.completedSteps.length)
22773
- });
22774
- }
22775
- function settleAction(input) {
22776
- if (!input.store || !input.taskRevision || !input.workspaceRevision)
22777
- return failure("invalid_input", "action settlement preconditions are required");
22778
- const authority = takeAuthority(input.authority, {
22779
- owner: input.authorityOwner,
22780
- store: input.store,
22781
- root: input.store.root
22782
- }, input.authorityCaller);
22783
- if (!authority)
22784
- return failure("permission_denied", "native settlement authority is not verified");
22785
- if (!refSchema.safeParse(input.actionRef).success)
22786
- return failure("invalid_input", "action reference is invalid");
22787
- if (!authorityMatches(authority, {
22788
- taskId: input.taskId,
22789
- workspaceId: authority.workspaceId,
22790
- decisionId: input.decisionId,
22791
- actionRef: input.actionRef,
22792
- taskRevision: input.taskRevision,
22793
- workspaceRevision: input.workspaceRevision,
22794
- outcome: input.outcome
22795
- }))
22796
- return failure("permission_denied", "native settlement authority is not verified");
22797
- let outcomeResult = null;
22798
- const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
22799
- const workspace = input.store.readWorkspace();
22800
- if (!workspace.ok)
22801
- return workspace;
22802
- if (!workspace.data)
22803
- return failure("not_found", "workspace not found");
22804
- if (!authorityMatches(authority, {
22805
- taskId: input.taskId,
22806
- workspaceId: workspace.data.id,
22807
- decisionId: input.decisionId,
22808
- actionRef: input.actionRef,
22809
- taskRevision: input.taskRevision,
22810
- workspaceRevision: input.workspaceRevision,
22811
- outcome: input.outcome
22812
- }))
22813
- return failure("permission_denied", "native settlement authority does not match context");
22814
- if (workspace.data.revision !== input.workspaceRevision)
22815
- return failure("revision_conflict", "workspace revision does not match", {
22816
- expectedWorkspaceRevision: input.workspaceRevision,
22817
- actualWorkspaceRevision: workspace.data.revision
22818
- });
22819
- const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
22820
- if (!entry)
22821
- return failure("not_found", "decision not found");
22822
- if (!authorityDecisionMatches(authority, entry.data))
22823
- return failure("permission_denied", "native settlement authority does not match decision");
22824
- if (!provenanceMatches(entry.provenance, authority.provenance))
22825
- return failure("permission_denied", "native settlement caller does not match approval");
22826
- if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
22827
- return failure("permission_denied", "decision binding is invalid");
22828
- if (entry.data.consumption?.state === "uncertain")
22829
- return failure("external_outcome_unknown", "previous action outcome is unknown", {
22830
- operation: "settle_action",
22831
- outcome: "unknown"
22832
- });
22833
- if (entry.data.consumption?.state !== "reserved")
22834
- return failure("permission_denied", "action is not reserved");
22835
- if (canonicalJson(entry.data.consumption.actionRef) !== canonicalJson(input.actionRef))
22836
- return failure("permission_denied", "settlement reference does not match reservation");
22837
- const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
22838
- const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: [], completed: [] };
22839
- if (entry.data.binding.approvedContent && !stored && (parseSteps(entry.data.binding.approvedContent) ?? []).length)
22840
- return failure("recovery_required", "bounded action progress is missing");
22841
- const currentStep = input.step ?? workflow.steps[workflow.completed.length];
22842
- if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
22843
- return failure("permission_denied", "settlement step is outside the approved workflow");
22844
- if (input.outcome === "unknown") {
22845
- outcomeResult = failure("external_outcome_unknown", "external action outcome is unknown", {
22846
- operation: "settle_action",
22847
- outcome: "unknown"
22848
- });
22849
- const uncertain = {
22850
- ...entry.data,
22851
- consumption: { ...entry.data.consumption, state: "uncertain", at: mutation.now }
22852
- };
22853
- return success(mutation.revision, null, {
22854
- ...current,
22855
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: uncertain } : candidate)
22856
- });
22783
+ var gitContext = (workspaceRoot, paths = []) => {
22784
+ const cwd = workspaceRoot;
22785
+ let failure;
22786
+ const runHere = (args) => {
22787
+ const result = run(cwd, args);
22788
+ if (!failure && result.exitCode !== 0) {
22789
+ failure = { stderr: result.stderr, exitCode: result.exitCode };
22857
22790
  }
22858
- if (input.outcome === "not_started") {
22859
- const released = { ...entry.data, consumption: null };
22860
- return success(mutation.revision, null, {
22861
- ...current,
22862
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: released } : candidate)
22863
- });
22791
+ return result.stdout;
22792
+ };
22793
+ const branch = runHere(["rev-parse", "--abbrev-ref", "HEAD"]) || "unknown";
22794
+ const status_short = runHere(["status", "--porcelain"]);
22795
+ const staged = [];
22796
+ const unstaged = [];
22797
+ const untracked = [];
22798
+ for (const line of status_short.split(`
22799
+ `).filter(Boolean)) {
22800
+ const code = line.slice(0, 2);
22801
+ const file = line.slice(3);
22802
+ if (code === "??") {
22803
+ untracked.push(file);
22804
+ } else {
22805
+ if (code[0] !== " " && code[0] !== "?")
22806
+ staged.push(file);
22807
+ if (code[1] !== " " && code[1] !== "?")
22808
+ unstaged.push(file);
22864
22809
  }
22865
- if (currentStep)
22866
- workflow.completed.push(currentStep);
22867
- const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
22868
- const settled = {
22869
- ...entry.data,
22870
- consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
22810
+ }
22811
+ const pathArgs = paths.length ? ["--", ...paths] : [];
22812
+ const diff_stat = runHere(["diff", "--stat", ...pathArgs]);
22813
+ const cached_stat = runHere(["diff", "--cached", "--stat", ...pathArgs]);
22814
+ const stagedSet = new Set(staged);
22815
+ const unstagedSet = new Set(unstaged);
22816
+ const partial_staged = [...stagedSet].filter((f) => unstagedSet.has(f));
22817
+ return {
22818
+ workspace_root: cwd,
22819
+ branch,
22820
+ status_short,
22821
+ staged,
22822
+ unstaged,
22823
+ untracked,
22824
+ diff_stat: [diff_stat, cached_stat].filter(Boolean).join(`
22825
+ `),
22826
+ partial_staged: partial_staged.length > 0,
22827
+ partial_staged_files: partial_staged,
22828
+ ...failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}
22829
+ };
22830
+ };
22831
+
22832
+ // packages/workit-core/src/core/vcs-config.ts
22833
+ import fs5 from "node:fs";
22834
+ import path7 from "node:path";
22835
+ import { spawnSync } from "node:child_process";
22836
+ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
22837
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
22838
+ var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22839
+ var remoteProvider = (cwd) => {
22840
+ const r = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22841
+ if (r.status !== 0)
22842
+ return null;
22843
+ const url = (r.stdout ?? "").trim();
22844
+ if (/github\.com[:/]/.test(url))
22845
+ return "github";
22846
+ if (/gitlab\.com[:/]/.test(url))
22847
+ return "gitlab";
22848
+ return null;
22849
+ };
22850
+ var readVcsConfig = () => {
22851
+ const file = vcsConfigPath();
22852
+ let raw;
22853
+ try {
22854
+ raw = fs5.readFileSync(file, "utf8");
22855
+ } catch {
22856
+ return { status: "missing", path: file, config: {} };
22857
+ }
22858
+ let parsed;
22859
+ try {
22860
+ parsed = JSON.parse(raw);
22861
+ } catch {
22862
+ return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
22863
+ }
22864
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
22865
+ return { status: "valid", path: file, config: parsed };
22866
+ }
22867
+ return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22868
+ };
22869
+ function vcsConfig(mode, cwd) {
22870
+ const ws = resolveWorkspace(vcsCwd(cwd));
22871
+ const wsVcs = ws?.vcs ?? {};
22872
+ const wsYt = ws?.youtrack ?? {};
22873
+ const wsIssues = ws?.issues ?? {};
22874
+ const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
22875
+ const root = vcsCwd(cwd);
22876
+ const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
22877
+ const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
22878
+ const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
22879
+ const wp = ws?.branchPolicy ?? {};
22880
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
22881
+ const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
22882
+ const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
22883
+ const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
22884
+ const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
22885
+ let issuesProvider = null;
22886
+ let linkOnPr = null;
22887
+ if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
22888
+ issuesProvider = "github";
22889
+ linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
22890
+ }
22891
+ if (mode === "resolve") {
22892
+ if (cfgStatus === "malformed")
22893
+ return { ok: false, error: cfgError, configPath: cfgPath };
22894
+ return {
22895
+ ok: true,
22896
+ workspace_name: ws?.name ?? null,
22897
+ provider,
22898
+ defaultTargetBranch: defaultTarget,
22899
+ link_issues: linkIssues,
22900
+ youtrack_base_url: youtrackBaseUrl,
22901
+ issues_provider: issuesProvider,
22902
+ link_on_pr: linkOnPr
22871
22903
  };
22872
- const actionProgress = workflow.steps.length ? [
22873
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
22874
- {
22875
- decisionId: input.decisionId,
22876
- steps: [...workflow.steps],
22877
- completedSteps: [...workflow.completed]
22878
- }
22879
- ] : current.actionProgress ?? [];
22880
- const next = {
22881
- ...current,
22882
- actionProgress,
22883
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: settled } : candidate)
22904
+ }
22905
+ if (cfgStatus === "malformed") {
22906
+ return { ok: false, error: cfgError, configPath: cfgPath };
22907
+ }
22908
+ if (cfgStatus === "missing") {
22909
+ return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
22910
+ }
22911
+ if (provider === null) {
22912
+ return {
22913
+ ok: false,
22914
+ error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
22915
+ configPath: cfgPath
22884
22916
  };
22885
- if (!complete) {
22886
- outcomeResult = success(mutation.revision, input.workspaceRevision, {
22887
- ...entry,
22888
- data: settled
22917
+ }
22918
+ const prov = cfg[provider] ?? {};
22919
+ const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
22920
+ const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
22921
+ const tokenPath = path7.resolve(tokenFile);
22922
+ let tokenOk = false;
22923
+ if (fs5.existsSync(tokenPath)) {
22924
+ const token = fs5.readFileSync(tokenPath, "utf8").trim();
22925
+ const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
22926
+ tokenOk = !placeholder;
22927
+ }
22928
+ const out = {
22929
+ ok: true,
22930
+ configPath: path7.resolve(cfgPath),
22931
+ provider,
22932
+ defaultTargetBranch: defaultTarget,
22933
+ pr: cfg.pr ?? {},
22934
+ tokenPath,
22935
+ tokenPresent: fs5.existsSync(tokenPath),
22936
+ tokenReady: tokenOk,
22937
+ workspace_name: ws?.name ?? null,
22938
+ link_issues: linkIssues,
22939
+ youtrack_base_url: youtrackBaseUrl,
22940
+ issues_provider: issuesProvider,
22941
+ link_on_pr: linkOnPr
22942
+ };
22943
+ if (provider === "gitlab") {
22944
+ out.gitlab = {
22945
+ host: prov.host ?? "gitlab.com",
22946
+ apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
22947
+ };
22948
+ } else if (provider === "github") {
22949
+ out.github = { host: prov.host ?? "github.com" };
22950
+ }
22951
+ if (mode === "summary")
22952
+ delete out.tokenPath;
22953
+ return out;
22954
+ }
22955
+ function mergedPrStyle(limit = 6, cwd) {
22956
+ const cfg = vcsConfig("load", cwd);
22957
+ if (!cfg.ok || !cfg.tokenReady)
22958
+ return { ok: false, error: "vcs not configured" };
22959
+ const provider = cfg.provider;
22960
+ const token = fs5.readFileSync(cfg.tokenPath, "utf8").trim();
22961
+ const examples = [];
22962
+ const descInfo = (desc, caseInsensitiveNotes = false) => ({
22963
+ hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
22964
+ sections: desc.split(`
22965
+ `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22966
+ descriptionPreview: desc.slice(0, 600)
22967
+ });
22968
+ if (provider === "gitlab") {
22969
+ const remote = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22970
+ if (remote.status !== 0)
22971
+ return { ok: false, error: "no origin remote" };
22972
+ const url = (remote.stdout ?? "").trim();
22973
+ const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
22974
+ if (!m)
22975
+ return { ok: false, error: "not a gitlab.com origin" };
22976
+ const project = m[1];
22977
+ const env = { ...process.env, GITLAB_TOKEN: token };
22978
+ const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
22979
+ let r = run([
22980
+ `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
22981
+ ]);
22982
+ if (r.status !== 0) {
22983
+ r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
22984
+ }
22985
+ if (r.status !== 0)
22986
+ return { ok: false, error: "could not list merge requests" };
22987
+ for (const mr of JSON.parse(r.stdout ?? "[]")) {
22988
+ const desc = String(mr.description ?? "").trim();
22989
+ examples.push({
22990
+ title: mr.title,
22991
+ url: mr.web_url,
22992
+ squash: mr.squash,
22993
+ ...descInfo(desc, true)
22889
22994
  });
22890
22995
  }
22891
- return success(mutation.revision, null, next);
22892
- }, input.native?.now);
22893
- if (!changed.ok)
22894
- return changed;
22895
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
22896
- if (!entry)
22897
- return failure("recovery_required", "settled decision disappeared");
22898
- if (outcomeResult)
22899
- return outcomeResult;
22900
- return success(changed.data.revision, input.workspaceRevision, entry);
22901
- }
22902
- function reconcileAction(input) {
22903
- if (!input.store || !input.taskRevision || !input.workspaceRevision || !input.evidenceDigest)
22904
- return failure("invalid_input", "action reconciliation preconditions are required");
22905
- const authority = takeAuthority(input.authority, { owner: input.authorityOwner, store: input.store, root: input.store.root }, input.authorityCaller);
22906
- if (!authority || authority.kind !== "reconciliation")
22907
- return failure("permission_denied", "native reconciliation authority is not verified");
22908
- if (authority.taskId !== input.taskId || authority.decisionId !== input.decisionId || !sameRef(authority.actionRef, input.actionRef) || authority.taskRevision !== input.taskRevision || authority.workspaceRevision !== input.workspaceRevision || authority.outcome !== input.outcome || authority.evidenceDigest !== input.evidenceDigest || authority.step !== input.step)
22909
- return failure("permission_denied", "native reconciliation authority is not bound");
22910
- const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
22911
- const workspace = input.store.readWorkspace();
22912
- if (!workspace.ok)
22913
- return workspace;
22914
- if (!workspace.data)
22915
- return failure("not_found", "workspace not found");
22916
- if (authority.workspaceId !== workspace.data.id)
22917
- return failure("permission_denied", "native reconciliation workspace does not match");
22918
- if (workspace.data.revision !== input.workspaceRevision)
22919
- return failure("revision_conflict", "workspace revision does not match");
22920
- const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
22921
- if (!entry || entry.data.purpose !== "action")
22922
- return failure("not_found", "decision not found");
22923
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
22924
- return failure("permission_denied", "native reconciliation caller does not match approval");
22925
- if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
22926
- return failure("permission_denied", "reconciliation decision binding is invalid");
22927
- if (entry.data.consumption?.state !== "uncertain")
22928
- return failure("external_outcome_unknown", "only an uncertain action can be reconciled");
22929
- if (!sameRef(entry.data.consumption.actionRef, input.actionRef))
22930
- return failure("permission_denied", "reconciliation reference does not match reservation");
22931
- const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
22932
- const workflow = stored ? { steps: [...stored.steps], completed: [...stored.completedSteps] } : { steps: [], completed: [] };
22933
- const currentStep = input.step ?? workflow.steps[workflow.completed.length];
22934
- if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
22935
- return failure("permission_denied", "reconciliation step is outside the approved workflow");
22936
- if (input.outcome === "not_started") {
22937
- return success(mutation.revision, null, {
22938
- ...current,
22939
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: { ...candidate.data, consumption: null } } : candidate)
22940
- });
22996
+ } else if (provider === "github") {
22997
+ const r = spawnSync("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
22998
+ if (r.status !== 0)
22999
+ return { ok: false, error: "could not list pull requests" };
23000
+ for (const pr of JSON.parse(r.stdout ?? "[]")) {
23001
+ const desc = String(pr.body ?? "").trim();
23002
+ examples.push({ title: pr.title, url: pr.url, ...descInfo(desc) });
22941
23003
  }
22942
- if (currentStep)
22943
- workflow.completed.push(currentStep);
22944
- const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
22945
- const nextDecision = {
22946
- ...entry.data,
22947
- consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
22948
- };
22949
- const actionProgress = workflow.steps.length ? [
22950
- ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
22951
- {
22952
- decisionId: input.decisionId,
22953
- steps: workflow.steps,
22954
- completedSteps: workflow.completed
22955
- }
22956
- ] : current.actionProgress ?? [];
22957
- return success(mutation.revision, null, {
22958
- ...current,
22959
- actionProgress,
22960
- decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
22961
- });
22962
- }, input.native?.now);
22963
- if (!changed.ok)
22964
- return changed;
22965
- const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
22966
- return entry ? success(changed.data.revision, input.workspaceRevision, entry) : failure("recovery_required", "reconciled decision disappeared");
22967
- }
22968
- var bindingCovers = scopeCovers;
22969
- var verifyDecisionContent = verifyContentRefs;
22970
-
22971
- // packages/workit-core/src/core/task-evaluation.ts
22972
- var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
22973
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
22974
- var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
22975
- var canonicalPath = (value) => {
22976
- if (typeof value !== "string" || !value)
22977
- return null;
22978
- if (value.includes("\\") || value.split("/").includes(".."))
22979
- return null;
22980
- const normalized = path6.posix.normalize(value);
22981
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
22982
- return null;
22983
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
22984
- };
22985
- var canonicalScope = (scope) => {
22986
- const paths = scope.paths.length ? scope.paths : ["."];
22987
- const normalizedPaths = paths.map(canonicalPath);
22988
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
22989
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
22990
- return null;
23004
+ }
22991
23005
  return {
22992
- description: scope.description,
22993
- paths: [...new Set(normalizedPaths)],
22994
- exclusions: [...new Set(normalizedExclusions)]
23006
+ ok: true,
23007
+ provider,
23008
+ count: examples.length,
23009
+ styleHints: [
23010
+ "Prefer ## Summary bullets + ## Validation or ## Test plan only",
23011
+ "Do not add ## Notes with branch names, commit counts, or diff stats",
23012
+ "Do not paste commit log or diff stat into the body"
23013
+ ],
23014
+ examples
22995
23015
  };
22996
- };
22997
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
22998
- var scopeMatches = (value, scope) => {
22999
- const normalized = canonicalScope(scope);
23000
- if (!normalized)
23001
- return false;
23002
- const paths = normalized.paths;
23003
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
23004
- };
23005
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
23006
- var compareCodeUnits2 = (left, right) => {
23007
- const length = Math.min(left.length, right.length);
23008
- for (let index = 0;index < length; index += 1) {
23009
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
23010
- if (difference !== 0)
23011
- return difference;
23016
+ }
23017
+
23018
+ // packages/workit-core/src/core/branch.ts
23019
+ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
23020
+ var verifyPushIdentity = (root, provider, account) => {
23021
+ if (!account || typeof account !== "string")
23022
+ return { ok: false, error: "push identity requires a configured area account" };
23023
+ const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
23024
+ const bin = provider === "gitlab" ? "glab" : "gh";
23025
+ let login;
23026
+ try {
23027
+ login = execFileSync2(bin, [...probe], {
23028
+ cwd: root,
23029
+ encoding: "utf8",
23030
+ stdio: ["pipe", "pipe", "pipe"]
23031
+ }).trim();
23032
+ } catch {
23033
+ return { ok: false, error: `${bin} identity could not be resolved` };
23012
23034
  }
23013
- return left.length - right.length;
23014
- };
23015
- var gitPaths = (root) => {
23016
- const result = spawnSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
23017
- cwd: root,
23018
- encoding: "buffer"
23019
- });
23020
- if (result.status !== 0) {
23021
- const stderr = result.stderr?.toString("utf8") ?? "";
23035
+ if (!login || login.toLowerCase() !== account.toLowerCase())
23022
23036
  return {
23023
- paths: [],
23024
- uncertain: !/not a git repository/i.test(stderr),
23025
- git: /not a git repository/i.test(stderr) ? false : true
23037
+ ok: false,
23038
+ error: `push identity ${login || "(unknown)"} does not match area account ${account}`
23026
23039
  };
23027
- }
23028
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
23029
- const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
23030
- if (stagedDeleted.status !== 0) {
23031
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
23032
- if (!/not a git repository/i.test(stderr))
23033
- return { paths, uncertain: true, git: true };
23034
- } else if (stagedDeleted.stdout) {
23035
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
23036
- }
23037
- return { paths: [...new Set(paths)], uncertain: false, git: true };
23040
+ return { ok: true };
23038
23041
  };
23039
- var walk = (root, directory, output) => {
23040
- let names;
23042
+
23043
+ // packages/workit-core/src/core/auto-approval.ts
23044
+ var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
23045
+ var OPERATION_CLASS = {
23046
+ "git.branch_setup": "branch",
23047
+ "git.commit": "commit",
23048
+ "git.push": "push",
23049
+ "hosting.pull_request": "pr",
23050
+ "hosting.merge": "merge"
23051
+ };
23052
+ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
23053
+ var digestOf = (configDirPath) => {
23041
23054
  try {
23042
- names = fs6.readdirSync(directory);
23055
+ const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
23056
+ return createHash3("sha256").update(raw).digest("hex");
23043
23057
  } catch {
23044
- return true;
23058
+ return null;
23045
23059
  }
23046
- let uncertain = false;
23047
- for (const name of names) {
23048
- if (name === ".git" || name === ".workit")
23049
- continue;
23050
- const target = path6.join(directory, name);
23051
- let stat;
23052
- try {
23053
- stat = fs6.lstatSync(target);
23054
- } catch {
23055
- uncertain = true;
23056
- continue;
23057
- }
23058
- const item = relative(root, target);
23059
- output.add(item);
23060
- if (stat.isDirectory() && !stat.isSymbolicLink())
23061
- uncertain = walk(root, target, output) || uncertain;
23060
+ };
23061
+ var normalizeClasses = (value) => {
23062
+ if (value === true)
23063
+ return [...AUTO_CLASSES];
23064
+ if (!Array.isArray(value))
23065
+ return [];
23066
+ const seen = new Set;
23067
+ for (const item of value) {
23068
+ if (typeof item === "string" && AUTO_CLASSES.includes(item))
23069
+ seen.add(item);
23062
23070
  }
23063
- return uncertain;
23071
+ return [...seen];
23064
23072
  };
23065
- var scopeRoots = (root, scope) => {
23066
- const roots = [];
23067
- for (const item of scope.paths.length ? scope.paths : ["."]) {
23068
- const target = path6.resolve(root, item);
23069
- if (!inside(root, target))
23070
- return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
23071
- let ancestor = target;
23072
- while (!fs6.existsSync(ancestor) && ancestor !== root)
23073
- ancestor = path6.dirname(ancestor);
23074
- try {
23075
- if (!inside(root, fs6.realpathSync(ancestor)))
23076
- return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
23077
- } catch {
23078
- return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
23079
- }
23080
- roots.push(target);
23073
+ var resolveAutoApproval = (root, dir = configDir()) => {
23074
+ let match;
23075
+ try {
23076
+ match = resolveWorkspaceFrom(root, dir);
23077
+ } catch {
23078
+ return { status: "off" };
23081
23079
  }
23082
- return success(null, null, roots);
23080
+ if (!match)
23081
+ return { status: "off" };
23082
+ const classes = normalizeClasses(match.autoApprove);
23083
+ if (classes.length === 0)
23084
+ return { status: "off" };
23085
+ return {
23086
+ status: "on",
23087
+ workspace: match.name,
23088
+ classes,
23089
+ account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
23090
+ configDigest: digestOf(dir)
23091
+ };
23083
23092
  };
23084
- function captureCandidate(root, scope, environment = []) {
23085
- let checkout;
23093
+ var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
23094
+ var standingApprovalLive = (root, receipt, cls, dir) => {
23095
+ if (!isStandingReceipt(receipt))
23096
+ return false;
23097
+ if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
23098
+ return false;
23099
+ if (receipt.class !== cls)
23100
+ return false;
23101
+ const resolved = resolveAutoApproval(root, dir);
23102
+ if (resolved.status !== "on")
23103
+ return false;
23104
+ if (resolved.workspace !== receipt.workspace)
23105
+ return false;
23106
+ return resolved.classes.includes(cls);
23107
+ };
23108
+ var standingReceiptFor = (root, workspace, cls, dir) => {
23109
+ const resolved = resolveAutoApproval(root, dir);
23110
+ return {
23111
+ kind: "standing",
23112
+ workspace,
23113
+ class: cls,
23114
+ configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
23115
+ };
23116
+ };
23117
+ var verifyStandingApproval = (root, task, caller, binding) => {
23118
+ if (task.status !== "active")
23119
+ return failure("invalid_transition", "only active tasks can authorize actions");
23120
+ const session = task.intent?.provenance?.session;
23121
+ if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
23122
+ return failure("permission_denied", "standing approval requires the lead session");
23123
+ const standing = binding.standing;
23124
+ if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
23125
+ return failure("invalid_input", "standing approval needs a workspace and class");
23126
+ let operation;
23086
23127
  try {
23087
- checkout = fs6.realpathSync(root);
23128
+ operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
23088
23129
  } catch {
23089
- return failure("invalid_input", "candidate root does not exist");
23130
+ operation = null;
23131
+ }
23132
+ const cls = operationAutoClass(operation);
23133
+ if (!cls || cls !== standing.class)
23134
+ return failure("permission_denied", "standing approval does not cover this operation");
23135
+ const resolved = resolveAutoApproval(root);
23136
+ if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
23137
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
23138
+ if (cls === "push") {
23139
+ const workspace = resolveWorkspaceFrom(root, configDir());
23140
+ const provider = workspace?.vcs?.provider;
23141
+ const account = resolved.account;
23142
+ if (!provider || !account)
23143
+ return failure("permission_denied", "auto-push requires a workspace provider and area account");
23144
+ const identity = verifyPushIdentity(root, provider, account);
23145
+ if (!identity.ok)
23146
+ return failure("permission_denied", identity.error);
23090
23147
  }
23091
- const normalizedScope = canonicalScope(scope);
23092
- if (!normalizedScope)
23093
- return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
23094
- const roots = scopeRoots(checkout, normalizedScope);
23095
- if (!roots.ok)
23096
- return roots;
23097
- const git = gitPaths(checkout);
23098
- const names = new Set;
23099
- let uncertain = git.uncertain;
23100
- for (const target of roots.data) {
23101
- const item = relative(checkout, target);
23102
- if (item === ".git" || item === ".workit")
23103
- continue;
23104
- names.add(item);
23105
- if (!fs6.existsSync(target))
23106
- continue;
23107
- let stat;
23108
- try {
23109
- stat = fs6.lstatSync(target);
23110
- } catch {
23111
- uncertain = true;
23148
+ return success(null, null, {
23149
+ kind: "host_observed",
23150
+ host: caller.host,
23151
+ session: { kind: "host", host: caller.host, handle: caller.actor },
23152
+ workerId: null,
23153
+ receipts: [standingReceiptFor(root, standing.workspace, cls)]
23154
+ });
23155
+ };
23156
+
23157
+ // packages/workit-core/src/core/authority.ts
23158
+ var verifiedAuthorities = new WeakMap;
23159
+ var trustedAuthority = (kind, provenance, expected, binding, caller) => {
23160
+ const token = {};
23161
+ verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
23162
+ return token;
23163
+ };
23164
+ var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
23165
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23166
+ var normalizeChainSteps = (steps) => {
23167
+ if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
23168
+ return null;
23169
+ const normalized = [];
23170
+ for (const step of steps) {
23171
+ if (typeof step === "string") {
23172
+ if (!step.trim())
23173
+ return null;
23174
+ normalized.push({ kind: "commit", message: step });
23112
23175
  continue;
23113
23176
  }
23114
- if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
23115
- uncertain = walk(checkout, target, names) || uncertain;
23116
- }
23117
- for (const item of git.paths)
23118
- if (scopeMatches(item, normalizedScope))
23119
- names.add(item);
23120
- const files = [];
23121
- let completeness = uncertain ? "uncertain" : "known";
23122
- for (const item of [...names].sort()) {
23123
- if (!scopeMatches(item, normalizedScope))
23177
+ if (!isRecord(step))
23178
+ return null;
23179
+ if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
23180
+ normalized.push({ kind: "branch", target: step.branch });
23181
+ continue;
23182
+ }
23183
+ if (step.pr === true && Object.keys(step).length === 1) {
23184
+ normalized.push({ kind: "pr" });
23124
23185
  continue;
23125
- const target = path6.join(checkout, item);
23126
- try {
23127
- const stat = fs6.lstatSync(target);
23128
- if (stat.isSymbolicLink()) {
23129
- const link = fs6.readlinkSync(target);
23130
- files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
23131
- } else if (stat.isFile()) {
23132
- files.push({
23133
- path: item,
23134
- kind: "file",
23135
- digest: digestBytes3(fs6.readFileSync(target)),
23136
- executable: (stat.mode & 73) !== 0
23137
- });
23138
- } else if (!stat.isDirectory()) {
23139
- completeness = "uncertain";
23140
- } else if (git.git && git.paths.includes(item)) {
23141
- completeness = "uncertain";
23142
- }
23143
- } catch (error) {
23144
- if (error?.code === "ENOENT") {
23145
- files.push({ path: item, kind: "absent", digest: null, executable: null });
23146
- } else
23147
- completeness = "uncertain";
23148
23186
  }
23187
+ return null;
23149
23188
  }
23150
- const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
23151
- name,
23152
- value: environment[name] ?? null
23153
- }));
23154
- const namesByEnvironment = supplied.map(({ name }) => name);
23155
- if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
23156
- return failure("invalid_input", "candidate environment names must be unique and non-empty");
23157
- const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
23158
- if (values.some(({ value }) => value === null))
23159
- completeness = "uncertain";
23160
- const headResult = spawnSync("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
23161
- if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
23162
- completeness = "uncertain";
23163
- const head = headResult.status === 0 ? headResult.stdout.trim() : null;
23164
- const partial = {
23165
- id: "0".repeat(64),
23166
- scope: normalizedScope,
23167
- completeness,
23168
- files,
23169
- environment: values,
23170
- head
23171
- };
23172
- const candidate = { ...partial, id: candidateDigest(partial) };
23173
- const parsed = candidateSchema.safeParse(candidate);
23174
- return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
23175
- }
23176
- var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
23177
- var changedPaths = (before, after) => {
23178
- const paths = new Set([...before.files, ...after.files].map((file) => file.path));
23179
- const left = fileMap(before);
23180
- const right = fileMap(after);
23181
- const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
23182
- if (before.head !== after.head)
23183
- changed.push(".");
23184
- if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
23185
- changed.push(".");
23186
- return changed;
23189
+ const keys = normalized.map(chainStepKey);
23190
+ if (new Set(keys).size !== keys.length)
23191
+ return null;
23192
+ return normalized;
23187
23193
  };
23188
- var sessionFromRef = (value) => {
23189
- if (typeof value !== "object" || value === null || value.kind !== "host")
23194
+ var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
23195
+ var parseSteps = (content) => {
23196
+ let value;
23197
+ try {
23198
+ value = JSON.parse(content);
23199
+ } catch {
23200
+ return [];
23201
+ }
23202
+ if (typeof value !== "object" || value === null || !("steps" in value))
23203
+ return [];
23204
+ const steps = value.steps;
23205
+ if (!Array.isArray(steps))
23190
23206
  return null;
23191
- return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
23207
+ const normalized = normalizeChainSteps(steps);
23208
+ return normalized;
23192
23209
  };
23193
- var sameSession = (left, right) => {
23194
- const other = sessionFromRef(right);
23195
- return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
23210
+ var decisionMatches = (entry, purpose, binding) => {
23211
+ const decision = entry.data;
23212
+ return binding.taskId === decision.binding.taskId && binding.workspaceId === decision.binding.workspaceId && decision.purpose === purpose && decision.response === "approved" && decision.revoked === null && (purpose !== "action" || decision.consumption === null) && decision.digest === decisionDigest(decision) && scopeEqual(decision.binding.scope, binding.scope) && canonicalJson(decision.binding) === canonicalJson(binding);
23196
23213
  };
23197
- var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
23198
- function evaluateEvidence(task, candidate) {
23199
- const current = candidate ?? task.candidates.at(-1) ?? null;
23200
- return task.evidence.map((entry) => {
23201
- const evidence = entry.data;
23202
- if (evidence.result === "failed")
23203
- return {
23204
- evidenceId: entry.id,
23205
- status: "failed",
23206
- reason: "failed evidence remains historical"
23207
- };
23208
- if (evidence.kind === "check" || evidence.kind === "review") {
23209
- if (!evidence.beforeCandidateId || !evidence.candidateId)
23210
- return {
23211
- evidenceId: entry.id,
23212
- status: "stale",
23213
- reason: "check or review evidence lacks start and end candidates"
23214
- };
23215
- if (evidence.beforeCandidateId !== evidence.candidateId)
23216
- return {
23217
- evidenceId: entry.id,
23218
- status: "stale",
23219
- reason: "candidate changed during the check or review"
23220
- };
23221
- }
23222
- if (!evidence.candidateId || !current)
23223
- return {
23224
- evidenceId: entry.id,
23225
- status: evidence.result,
23226
- reason: "evidence has no candidate binding"
23227
- };
23228
- const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
23229
- if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
23230
- return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
23231
- if (observed.id === current.id)
23232
- return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
23233
- const scopes = evidenceScopes(task, evidence.requirementIds);
23234
- const changed = changedPaths(observed, current);
23235
- const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
23236
- if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
23237
- return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
23238
- return {
23239
- evidenceId: entry.id,
23240
- status: evidence.result,
23241
- reason: "candidate changed outside evidence scope"
23242
- };
23243
- });
23244
- }
23245
- var scopeCovers2 = scopeCovers;
23246
- var checkPin = (assignment, beforeCandidateId, candidateId) => {
23247
- const pin = assignment.candidateId ?? null;
23248
- if (pin === null)
23249
- return true;
23250
- return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
23214
+ var sameRef = (left, right) => canonicalJson(left) === canonicalJson(right);
23215
+ var validProvenance = (value, caller) => {
23216
+ if (!provenanceSchema.safeParse(value).success)
23217
+ return false;
23218
+ const provenance = value;
23219
+ return provenance.kind === "host_observed" && provenance.host === caller.host && provenance.session?.kind === "host" && provenance.session.host === caller.host && provenance.session.handle === caller.actor && provenance.receipts.some((receipt) => receipt.kind === "host" && receipt.host === caller.host);
23251
23220
  };
23252
- var resolveBinding = (evidence, pin, currentCandidateId) => {
23253
- const bound = evidence.kind === "check" || evidence.kind === "review";
23254
- const fallback = bound ? pin ?? currentCandidateId : null;
23255
- return {
23256
- beforeCandidateId: evidence.beforeCandidateId ?? fallback,
23257
- candidateId: evidence.candidateId ?? fallback
23258
- };
23221
+ var verifyNativeDecision = (verifier, input, binding) => {
23222
+ if (!verifier)
23223
+ return failure("permission_denied", "native authority verifier is unavailable");
23224
+ const result = verifier.verifyDecision(input);
23225
+ if (!result.ok || !validProvenance(result.data, input.caller))
23226
+ return failure("permission_denied", "native decision observation was not attested");
23227
+ return success(null, null, trustedAuthority("decision", result.data, {
23228
+ taskId: input.expected.taskId,
23229
+ workspaceId: input.expected.workspaceId,
23230
+ purpose: input.expected.purpose,
23231
+ response: input.expected.response,
23232
+ binding: input.expected.binding,
23233
+ bindingBytes: input.expected.bindingBytes,
23234
+ digest: input.expected.digest,
23235
+ requirementIds: [...input.expected.requirementIds]
23236
+ }, binding, input.caller));
23259
23237
  };
23260
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
23261
- var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
23262
- var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
23263
- var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
23264
- if (kind !== "passed")
23265
- return false;
23266
- if (dimension === "testing" || dimension === "verification")
23267
- return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
23268
- if (dimension === "review")
23269
- return evidenceKind === "review";
23270
- if (dimension === "investigation" || dimension === "challenge")
23271
- return evidenceKind === "investigation";
23272
- if (dimension === "artifacts" || dimension === "continuity")
23273
- return evidenceKind === "artifact";
23274
- return false;
23238
+ var verifyNativeAction = (verifier, input, binding) => {
23239
+ if (!verifier)
23240
+ return failure("permission_denied", "native authority verifier is unavailable");
23241
+ const result = verifier.verifyAction(input);
23242
+ if (!result.ok || !validProvenance(result.data, input.caller))
23243
+ return failure("permission_denied", "native action observation was not attested");
23244
+ return success(null, null, trustedAuthority("action", result.data, {
23245
+ taskId: input.expected.taskId,
23246
+ workspaceId: input.expected.workspaceId,
23247
+ decisionId: input.expected.decisionId,
23248
+ actionRef: input.expected.actionRef,
23249
+ taskRevision: input.expected.taskRevision,
23250
+ workspaceRevision: input.expected.workspaceRevision,
23251
+ outcome: input.expected.outcome,
23252
+ purpose: input.expected.decision.purpose,
23253
+ response: input.expected.decision.response,
23254
+ binding: input.expected.decision.binding,
23255
+ digest: input.expected.decision.digest,
23256
+ requirementIds: [...input.expected.decision.requirementIds]
23257
+ }, binding, input.caller));
23275
23258
  };
23276
- var expectedKinds = (dimension, ruleId) => {
23277
- if (dimension === "testing")
23278
- return "check";
23279
- if (dimension === "verification")
23280
- return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
23281
- if (dimension === "review")
23282
- return "review";
23283
- if (dimension === "investigation" || dimension === "challenge")
23284
- return "investigation";
23285
- if (dimension === "artifacts" || dimension === "continuity")
23286
- return "artifact";
23287
- if (dimension === "decisions")
23288
- return "decision";
23289
- return "worker report";
23259
+ var verifyNativeReconciliation = (verifier, input, binding) => {
23260
+ if (!verifier?.verifyReconciliation)
23261
+ return failure("permission_denied", "native reconciliation authority is unavailable");
23262
+ const result = verifier.verifyReconciliation(input);
23263
+ if (!result.ok || !validProvenance(result.data, input.caller))
23264
+ return failure("permission_denied", "native reconciliation observation was not attested");
23265
+ return success(null, null, trustedAuthority("reconciliation", result.data, {
23266
+ taskId: input.expected.taskId,
23267
+ workspaceId: input.expected.workspaceId,
23268
+ decisionId: input.expected.decisionId,
23269
+ actionRef: input.expected.actionRef,
23270
+ taskRevision: input.expected.taskRevision,
23271
+ workspaceRevision: input.expected.workspaceRevision,
23272
+ outcome: input.expected.outcome,
23273
+ evidenceDigest: input.expected.evidenceDigest,
23274
+ step: input.expected.step,
23275
+ purpose: input.expected.decision.purpose,
23276
+ response: input.expected.decision.response,
23277
+ binding: input.expected.decision.binding,
23278
+ digest: input.expected.decision.digest,
23279
+ requirementIds: [...input.expected.decision.requirementIds]
23280
+ }, binding, input.caller));
23290
23281
  };
23291
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
23292
- const evidence = evaluateEvidence(task, candidate);
23293
- if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
23294
- return task.policy.requirements.map((requirement) => ({
23295
- requirementId: requirement.id,
23296
- status: "unsatisfied",
23297
- evidenceIds: [],
23298
- decisionIds: [],
23299
- reason: "stored policy version is unsupported; reassessment is required"
23300
- }));
23301
- return (task.policy?.requirements ?? []).map((requirement) => {
23302
- const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
23303
- const passed = related.filter(({ entry, evaluation }) => {
23304
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
23305
- return false;
23306
- if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
23307
- return false;
23308
- if (requirement.dimension !== "review")
23309
- return true;
23310
- const reviewSession = sessionFromRef(entry.data.reviewContext);
23311
- const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
23312
- const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
23313
- return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
23314
- });
23315
- if (passed.length) {
23316
- const currentId = candidate?.id ?? null;
23317
- const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
23318
- if (withRed.length)
23319
- return {
23320
- requirementId: requirement.id,
23321
- status: "satisfied",
23322
- evidenceIds: withRed.map(({ entry }) => entry.id),
23323
- decisionIds: [],
23324
- reason: "fresh applicable evidence passed"
23325
- };
23326
- if (requirement.dimension === "testing")
23327
- return {
23328
- requirementId: requirement.id,
23329
- status: "unsatisfied",
23330
- evidenceIds: [],
23331
- decisionIds: [],
23332
- reason: "testing requirement has GREEN evidence but no preceding RED"
23333
- };
23334
- }
23335
- if (requirement.dimension === "delegation") {
23336
- const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
23337
- const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
23338
- if (reconciled)
23339
- return {
23340
- requirementId: requirement.id,
23341
- status: "satisfied",
23342
- evidenceIds: [],
23343
- decisionIds: [],
23344
- reason: "bounded helper completed and its result is recorded"
23345
- };
23346
- }
23347
- const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
23348
- if (decisions.length)
23349
- return {
23350
- requirementId: requirement.id,
23351
- status: "satisfied",
23352
- evidenceIds: [],
23353
- decisionIds: decisions.map((decision) => decision.id),
23354
- reason: "an applicable approved or stated decision satisfies the requirement"
23355
- };
23356
- const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
23357
- if (limitations.length)
23358
- return {
23359
- requirementId: requirement.id,
23360
- status: "accepted_limitation",
23361
- evidenceIds: [],
23362
- decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
23363
- reason: "an applicable approved limitation permits the missing evidence"
23364
- };
23365
- const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
23366
- if (!unavailable && related.length > 0 && !passed.length)
23367
- return {
23368
- requirementId: requirement.id,
23369
- status: "unsatisfied",
23370
- evidenceIds: related.map(({ entry }) => entry.id),
23371
- decisionIds: [],
23372
- reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
23373
- };
23374
- return {
23375
- requirementId: requirement.id,
23376
- status: unavailable ? "unavailable" : "unsatisfied",
23377
- evidenceIds: related.map(({ entry }) => entry.id),
23378
- decisionIds: [],
23379
- reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
23380
- };
23381
- });
23282
+ var takeAuthority = (authority, binding, caller) => {
23283
+ const record = verifiedAuthorities.get(authority);
23284
+ verifiedAuthorities.delete(authority);
23285
+ if (!record)
23286
+ return null;
23287
+ if (record.owner !== binding.owner || record.store !== binding.store || record.root !== binding.root || record.root !== record.store.root || canonicalJson(record.caller) !== canonicalJson(caller))
23288
+ return null;
23289
+ return record;
23290
+ };
23291
+ var retireNativeAuthority = (authority) => {
23292
+ const record = verifiedAuthorities.get(authority);
23293
+ verifiedAuthorities.delete(authority);
23294
+ return record?.provenance ?? null;
23295
+ };
23296
+ var authorityMatches = (authority, expected) => authority.kind === "action" && authority.taskId === expected.taskId && authority.workspaceId === expected.workspaceId && authority.decisionId === expected.decisionId && sameRef(authority.actionRef, expected.actionRef) && authority.taskRevision === expected.taskRevision && authority.workspaceRevision === expected.workspaceRevision && authority.outcome === expected.outcome;
23297
+ var authorityDecisionMatches = (authority, decision) => authority.purpose === decision.purpose && authority.response === decision.response && authority.digest === decision.digest && canonicalJson(authority.binding) === canonicalJson(decision.binding) && canonicalJson(authority.requirementIds) === canonicalJson(decision.requirementIds);
23298
+ var provenanceMatches = (left, right) => left.kind === "host_observed" && right.kind === "host_observed" && left.host === right.host && canonicalJson(left.session) === canonicalJson(right.session);
23299
+ function applicableDecision(task, purpose, binding, checkoutRoot) {
23300
+ if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
23301
+ return [];
23302
+ return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
23382
23303
  }
23383
- function evaluateClosure(requestedOutcome, view) {
23384
- if (requestedOutcome !== "stopped" && !view.task.policy)
23385
- return failure("requirements_unsatisfied", "task has not been assessed");
23386
- if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
23387
- return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
23388
- const evaluations = view.requirements;
23389
- const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
23390
- if (requestedOutcome !== "stopped" && openFindings.length)
23391
- return failure("requirements_unsatisfied", "open findings must be resolved before closure");
23392
- if (requestedOutcome !== "stopped") {
23393
- const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
23394
- for (const entry of view.task.findings) {
23395
- if (entry.data.disposition === "fixed") {
23396
- const verified = entry.data.resolution?.evidenceIds.some((id) => {
23397
- const evidence = view.task.evidence.find((item) => item.id === id);
23398
- if (!evidence)
23399
- return false;
23400
- return findingVerificationPasses(entry.data.candidateId, {
23401
- kind: evidence.data.kind,
23402
- candidateId: evidence.data.candidateId,
23403
- status: evidenceById.get(id) ?? "missing"
23304
+ var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
23305
+ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23306
+ let root;
23307
+ try {
23308
+ root = fs6.realpathSync(checkoutRoot);
23309
+ } catch {
23310
+ return failure("permission_denied", "checkout root is unavailable");
23311
+ }
23312
+ for (const reference of binding.contentRefs) {
23313
+ if (reference.kind !== "file")
23314
+ continue;
23315
+ if (!reference.digest)
23316
+ return failure("invalid_input", "document references require a byte digest");
23317
+ const target = path8.resolve(root, reference.path);
23318
+ if (target !== root && !target.startsWith(`${root}${path8.sep}`))
23319
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23320
+ fields: [
23321
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
23322
+ ]
23323
+ });
23324
+ try {
23325
+ const relative = path8.relative(root, target);
23326
+ let current = root;
23327
+ for (const segment of relative.split(path8.sep)) {
23328
+ if (!segment)
23329
+ continue;
23330
+ current = path8.join(current, segment);
23331
+ const stat = fs6.lstatSync(current);
23332
+ const real = fs6.realpathSync(current);
23333
+ if (real !== root && !real.startsWith(`${root}${path8.sep}`))
23334
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23335
+ fields: [
23336
+ {
23337
+ path: "contentRefs.path",
23338
+ reason: `${reference.path} resolves outside the checkout`
23339
+ }
23340
+ ]
23404
23341
  });
23405
- });
23406
- if (!verified)
23407
- return failure("requirements_unsatisfied", "fixed findings require current verification");
23342
+ if (stat.isSymbolicLink())
23343
+ return failure("invalid_input", "document reference uses a symlink");
23344
+ if (current === target && !stat.isFile())
23345
+ return failure("invalid_input", "document reference is not a regular file");
23346
+ if (current !== target && !stat.isDirectory())
23347
+ return failure("invalid_input", "document reference ancestor is not a directory");
23408
23348
  }
23409
- if (entry.data.disposition === "deferred") {
23410
- const valid = entry.data.resolution?.decisionIds.some((id) => {
23411
- const decisionEntry = view.task.decisions.find((item) => item.id === id);
23412
- const decision = decisionEntry?.data;
23413
- return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
23414
- });
23415
- if (!valid)
23416
- return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
23349
+ if (digestBytes2(fs6.readFileSync(target)) !== reference.digest)
23350
+ return failure("permission_denied", "approved document bytes have changed");
23351
+ } catch {
23352
+ return failure("invalid_input", "approved document is unavailable");
23353
+ }
23354
+ }
23355
+ return success(null, null, null);
23356
+ };
23357
+ var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
23358
+ var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
23359
+ var validateAction = (store, task, workspaceId, input, authority) => {
23360
+ if (task.status !== "active")
23361
+ return failure("invalid_transition", "only active tasks can authorize actions");
23362
+ if (!refSchema.safeParse(input.actionRef).success)
23363
+ return failure("invalid_input", "action reference is invalid");
23364
+ const entry = task.decisions.find((candidate) => candidate.id === input.decisionId);
23365
+ if (!entry)
23366
+ return failure("not_found", "decision not found");
23367
+ const decision = entry.data;
23368
+ if (decision.purpose !== "action")
23369
+ return failure("permission_denied", "decision is not an action approval");
23370
+ if (!authorityMatches(authority, {
23371
+ taskId: task.id,
23372
+ workspaceId,
23373
+ decisionId: input.decisionId,
23374
+ actionRef: input.actionRef,
23375
+ taskRevision: input.expectedRevision,
23376
+ workspaceRevision: input.expectedWorkspaceRevision,
23377
+ outcome: "reserve"
23378
+ }))
23379
+ return failure("permission_denied", "native reservation authority is not bound");
23380
+ if (!authorityDecisionMatches(authority, decision))
23381
+ return failure("permission_denied", "native reservation authority does not match decision");
23382
+ if (decision.response !== "approved")
23383
+ return failure("permission_denied", "decision was rejected");
23384
+ if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
23385
+ return failure("permission_denied", "action approval lacks native receipt assurance");
23386
+ const receipts = entry.provenance.receipts;
23387
+ const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
23388
+ const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
23389
+ if (!questionBacked && standing.length > 0) {
23390
+ let operation;
23391
+ try {
23392
+ operation = JSON.parse(decision.binding.approvedContent).operation;
23393
+ } catch {
23394
+ operation = null;
23395
+ }
23396
+ const cls = operationAutoClass(operation);
23397
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
23398
+ return failure("permission_denied", "standing auto-approval is not live for this operation");
23399
+ }
23400
+ if (decision.revoked)
23401
+ return failure("permission_denied", "decision is revoked");
23402
+ if (decision.digest !== decisionDigest(decision))
23403
+ return failure("permission_denied", "decision binding is invalid");
23404
+ if (decision.binding.taskId !== task.id || decision.binding.workspaceId !== workspaceId)
23405
+ return failure("permission_denied", "decision task or workspace binding is invalid");
23406
+ if (input.binding && canonicalJson(input.binding) !== canonicalJson(decision.binding))
23407
+ return failure("permission_denied", "action binding does not match the approved decision");
23408
+ const content = verifyContentRefs(store, decision.binding);
23409
+ if (!content.ok)
23410
+ return content;
23411
+ const requestedSteps = parseSteps(decision.binding.approvedContent);
23412
+ if (requestedSteps === null)
23413
+ return failure("invalid_input", "bounded action steps must be a unique non-empty array");
23414
+ if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
23415
+ return failure("permission_denied", "bounded action steps do not match approved content");
23416
+ const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
23417
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
23418
+ if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
23419
+ return failure("permission_denied", "bounded action scope changed");
23420
+ return success(null, null, { entry, workflow });
23421
+ };
23422
+ function reserveAction(input) {
23423
+ if (!input.store || !input.expectedRevision || !input.expectedWorkspaceRevision)
23424
+ return failure("invalid_input", "action reservation preconditions are required");
23425
+ const authority = takeAuthority(input.authority, {
23426
+ owner: input.authorityOwner,
23427
+ store: input.store,
23428
+ root: input.store.root
23429
+ }, input.authorityCaller);
23430
+ if (!authority)
23431
+ return failure("permission_denied", "native reservation authority is not verified");
23432
+ const changed = input.store.mutateTask(input.taskId, input.expectedRevision, (current, mutation) => {
23433
+ const workspace = input.store.readWorkspace();
23434
+ if (!workspace.ok)
23435
+ return workspace;
23436
+ if (!workspace.data)
23437
+ return failure("not_found", "workspace not found");
23438
+ if (workspace.data.revision !== input.expectedWorkspaceRevision)
23439
+ return failure("revision_conflict", "workspace revision does not match", {
23440
+ expectedWorkspaceRevision: input.expectedWorkspaceRevision,
23441
+ actualWorkspaceRevision: workspace.data.revision
23442
+ });
23443
+ const valid = validateAction(input.store, current, workspace.data.id, input, authority);
23444
+ if (!valid.ok)
23445
+ return valid;
23446
+ const { entry, workflow } = valid.data;
23447
+ const existing = entry.data.consumption;
23448
+ if (existing?.state === "uncertain")
23449
+ return failure("external_outcome_unknown", "previous action outcome is unknown", {
23450
+ operation: "reserve_action",
23451
+ outcome: "unknown"
23452
+ });
23453
+ if (existing?.state === "consumed")
23454
+ return failure("permission_denied", "action approval is consumed");
23455
+ if (existing?.state === "reserved" && workflow.completed.length === 0)
23456
+ return failure("permission_denied", "action approval is already reserved");
23457
+ const requestedStep = input.step ?? workflow.steps[workflow.completed.length];
23458
+ if (workflow.steps.length && (!requestedStep || requestedStep !== workflow.steps[workflow.completed.length]))
23459
+ return failure("permission_denied", "action step is outside the approved remaining workflow");
23460
+ if (requestedStep && workflow.completed.includes(requestedStep))
23461
+ return failure("permission_denied", "action step was already completed");
23462
+ const nextDecision = {
23463
+ ...entry.data,
23464
+ consumption: { state: "reserved", at: mutation.now, actionRef: input.actionRef }
23465
+ };
23466
+ const actionProgress = workflow.steps.length ? [
23467
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
23468
+ {
23469
+ decisionId: input.decisionId,
23470
+ steps: [...workflow.steps],
23471
+ completedSteps: [...workflow.completed]
23417
23472
  }
23418
- }
23419
- }
23420
- const blocking = evaluations.filter((item) => {
23421
- if (item.status !== "unsatisfied" && item.status !== "unavailable")
23422
- return false;
23423
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
23424
- return !requirement || requirement.before === "close";
23425
- });
23426
- if (requestedOutcome !== "stopped" && blocking.length)
23427
- return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
23428
- requirementIds: blocking.map((item) => item.requirementId),
23429
- requirements: blocking.map((item) => {
23430
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
23431
- return {
23432
- requirementId: item.requirementId,
23433
- ruleId: requirement?.ruleId ?? "unknown",
23434
- reason: item.reason,
23435
- satisfaction: requirement?.satisfaction ?? "",
23436
- dependentAction: requirement?.dependentAction ?? null
23437
- };
23438
- })
23439
- });
23440
- const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
23441
- if (requestedOutcome === "verified" && accepted.length)
23442
- return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
23443
- requirementIds: accepted.map((item) => item.requirementId)
23444
- });
23445
- const evidenceIds = [
23446
- ...new Set([
23447
- ...evaluations.flatMap((item) => item.evidenceIds),
23448
- ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
23449
- ])
23450
- ];
23451
- const decisionIds = [
23452
- ...new Set([
23453
- ...evaluations.flatMap((item) => item.decisionIds),
23454
- ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
23455
- ])
23456
- ];
23457
- return success(null, null, {
23458
- outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
23459
- evidenceIds,
23460
- decisionIds,
23461
- requirementIds: evaluations.map((item) => item.requirementId)
23462
- });
23463
- }
23464
-
23465
- // packages/workit-core/src/core/task-context.ts
23466
- function reconcileResume(view, observations = []) {
23467
- if (observations.length > 0)
23468
- return failure("permission_denied", "worker observations require native host verification");
23469
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
23470
- if (!candidate.ok)
23471
- return candidate;
23472
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
23473
- const blockers = [...view.task.progress.blockers];
23474
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
23475
- blockers.push({
23476
- reason: "worker state requires reconciliation",
23477
- dependentAction: "resume",
23478
- refs: []
23473
+ ] : current.actionProgress ?? [];
23474
+ return success(mutation.revision, null, {
23475
+ ...current,
23476
+ actionProgress,
23477
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
23479
23478
  });
23480
- return success(null, null, {
23481
- candidate: candidate.data,
23482
- staleEvidenceIds,
23483
- workerUpdates: [],
23484
- blockers,
23485
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
23479
+ }, input.native?.now);
23480
+ if (!changed.ok)
23481
+ return changed;
23482
+ const workspace = input.store.readWorkspace();
23483
+ if (!workspace.ok || !workspace.data)
23484
+ return failure("recovery_required", "workspace disappeared after reservation");
23485
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
23486
+ if (!entry)
23487
+ return failure("recovery_required", "reserved decision disappeared");
23488
+ const workflow = changed.data.actionProgress?.find((progress) => progress.decisionId === input.decisionId) ?? {
23489
+ steps: [],
23490
+ completedSteps: []
23491
+ };
23492
+ return success(changed.data.revision, workspace.data.revision, {
23493
+ taskId: input.taskId,
23494
+ decisionId: input.decisionId,
23495
+ actionRef: input.actionRef,
23496
+ taskRevision: changed.data.revision,
23497
+ workspaceRevision: workspace.data.revision,
23498
+ completedSteps: [...workflow.completedSteps],
23499
+ remainingSteps: workflow.steps.slice(workflow.completedSteps.length)
23486
23500
  });
23487
23501
  }
23488
- var exportDigest = (bundle) => sha256({
23489
- schemaVersion: bundle.schemaVersion,
23490
- exportedAt: bundle.exportedAt,
23491
- sourceWorkspaceId: bundle.sourceWorkspaceId,
23492
- task: bundle.task
23493
- });
23494
- // packages/workit-core/src/core/git.ts
23495
- import { execFileSync } from "node:child_process";
23496
- var run = (cwd, args) => {
23497
- try {
23498
- const stdout = execFileSync("git", args, {
23499
- cwd,
23500
- encoding: "utf8",
23501
- stdio: ["pipe", "pipe", "pipe"]
23502
- });
23503
- return { stdout: stdout.trimEnd(), stderr: "", exitCode: 0 };
23504
- } catch (error) {
23505
- const e = error;
23506
- return {
23507
- stdout: (e.stdout ?? "").trimEnd(),
23508
- stderr: (e.stderr ?? "").trimEnd(),
23509
- exitCode: e.status ?? 1
23502
+ function settleAction(input) {
23503
+ if (!input.store || !input.taskRevision || !input.workspaceRevision)
23504
+ return failure("invalid_input", "action settlement preconditions are required");
23505
+ const authority = takeAuthority(input.authority, {
23506
+ owner: input.authorityOwner,
23507
+ store: input.store,
23508
+ root: input.store.root
23509
+ }, input.authorityCaller);
23510
+ if (!authority)
23511
+ return failure("permission_denied", "native settlement authority is not verified");
23512
+ if (!refSchema.safeParse(input.actionRef).success)
23513
+ return failure("invalid_input", "action reference is invalid");
23514
+ if (!authorityMatches(authority, {
23515
+ taskId: input.taskId,
23516
+ workspaceId: authority.workspaceId,
23517
+ decisionId: input.decisionId,
23518
+ actionRef: input.actionRef,
23519
+ taskRevision: input.taskRevision,
23520
+ workspaceRevision: input.workspaceRevision,
23521
+ outcome: input.outcome
23522
+ }))
23523
+ return failure("permission_denied", "native settlement authority is not verified");
23524
+ let outcomeResult = null;
23525
+ const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
23526
+ const workspace = input.store.readWorkspace();
23527
+ if (!workspace.ok)
23528
+ return workspace;
23529
+ if (!workspace.data)
23530
+ return failure("not_found", "workspace not found");
23531
+ if (!authorityMatches(authority, {
23532
+ taskId: input.taskId,
23533
+ workspaceId: workspace.data.id,
23534
+ decisionId: input.decisionId,
23535
+ actionRef: input.actionRef,
23536
+ taskRevision: input.taskRevision,
23537
+ workspaceRevision: input.workspaceRevision,
23538
+ outcome: input.outcome
23539
+ }))
23540
+ return failure("permission_denied", "native settlement authority does not match context");
23541
+ if (workspace.data.revision !== input.workspaceRevision)
23542
+ return failure("revision_conflict", "workspace revision does not match", {
23543
+ expectedWorkspaceRevision: input.workspaceRevision,
23544
+ actualWorkspaceRevision: workspace.data.revision
23545
+ });
23546
+ const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
23547
+ if (!entry)
23548
+ return failure("not_found", "decision not found");
23549
+ if (!authorityDecisionMatches(authority, entry.data))
23550
+ return failure("permission_denied", "native settlement authority does not match decision");
23551
+ if (!provenanceMatches(entry.provenance, authority.provenance))
23552
+ return failure("permission_denied", "native settlement caller does not match approval");
23553
+ if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
23554
+ return failure("permission_denied", "decision binding is invalid");
23555
+ if (entry.data.consumption?.state === "uncertain")
23556
+ return failure("external_outcome_unknown", "previous action outcome is unknown", {
23557
+ operation: "settle_action",
23558
+ outcome: "unknown"
23559
+ });
23560
+ if (entry.data.consumption?.state !== "reserved")
23561
+ return failure("permission_denied", "action is not reserved");
23562
+ if (canonicalJson(entry.data.consumption.actionRef) !== canonicalJson(input.actionRef))
23563
+ return failure("permission_denied", "settlement reference does not match reservation");
23564
+ const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
23565
+ const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: [], completed: [] };
23566
+ if (entry.data.binding.approvedContent && !stored && (parseSteps(entry.data.binding.approvedContent) ?? []).length)
23567
+ return failure("recovery_required", "bounded action progress is missing");
23568
+ const currentStep = input.step ?? workflow.steps[workflow.completed.length];
23569
+ if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
23570
+ return failure("permission_denied", "settlement step is outside the approved workflow");
23571
+ if (input.outcome === "unknown") {
23572
+ outcomeResult = failure("external_outcome_unknown", "external action outcome is unknown", {
23573
+ operation: "settle_action",
23574
+ outcome: "unknown"
23575
+ });
23576
+ const uncertain = {
23577
+ ...entry.data,
23578
+ consumption: { ...entry.data.consumption, state: "uncertain", at: mutation.now }
23579
+ };
23580
+ return success(mutation.revision, null, {
23581
+ ...current,
23582
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: uncertain } : candidate)
23583
+ });
23584
+ }
23585
+ if (input.outcome === "not_started") {
23586
+ const released = { ...entry.data, consumption: null };
23587
+ return success(mutation.revision, null, {
23588
+ ...current,
23589
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: released } : candidate)
23590
+ });
23591
+ }
23592
+ if (currentStep)
23593
+ workflow.completed.push(currentStep);
23594
+ const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
23595
+ const settled = {
23596
+ ...entry.data,
23597
+ consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
23598
+ };
23599
+ const actionProgress = workflow.steps.length ? [
23600
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
23601
+ {
23602
+ decisionId: input.decisionId,
23603
+ steps: [...workflow.steps],
23604
+ completedSteps: [...workflow.completed]
23605
+ }
23606
+ ] : current.actionProgress ?? [];
23607
+ const next = {
23608
+ ...current,
23609
+ actionProgress,
23610
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: settled } : candidate)
23510
23611
  };
23511
- }
23512
- };
23513
- var gitContext = (workspaceRoot, paths = []) => {
23514
- const cwd = workspaceRoot;
23515
- let failure;
23516
- const runHere = (args) => {
23517
- const result = run(cwd, args);
23518
- if (!failure && result.exitCode !== 0) {
23519
- failure = { stderr: result.stderr, exitCode: result.exitCode };
23612
+ if (!complete) {
23613
+ outcomeResult = success(mutation.revision, input.workspaceRevision, {
23614
+ ...entry,
23615
+ data: settled
23616
+ });
23520
23617
  }
23521
- return result.stdout;
23522
- };
23523
- const branch = runHere(["rev-parse", "--abbrev-ref", "HEAD"]) || "unknown";
23524
- const status_short = runHere(["status", "--porcelain"]);
23525
- const staged = [];
23526
- const unstaged = [];
23527
- const untracked = [];
23528
- for (const line of status_short.split(`
23529
- `).filter(Boolean)) {
23530
- const code = line.slice(0, 2);
23531
- const file = line.slice(3);
23532
- if (code === "??") {
23533
- untracked.push(file);
23534
- } else {
23535
- if (code[0] !== " " && code[0] !== "?")
23536
- staged.push(file);
23537
- if (code[1] !== " " && code[1] !== "?")
23538
- unstaged.push(file);
23618
+ return success(mutation.revision, null, next);
23619
+ }, input.native?.now);
23620
+ if (!changed.ok)
23621
+ return changed;
23622
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
23623
+ if (!entry)
23624
+ return failure("recovery_required", "settled decision disappeared");
23625
+ if (outcomeResult)
23626
+ return outcomeResult;
23627
+ return success(changed.data.revision, input.workspaceRevision, entry);
23628
+ }
23629
+ function reconcileAction(input) {
23630
+ if (!input.store || !input.taskRevision || !input.workspaceRevision || !input.evidenceDigest)
23631
+ return failure("invalid_input", "action reconciliation preconditions are required");
23632
+ const authority = takeAuthority(input.authority, { owner: input.authorityOwner, store: input.store, root: input.store.root }, input.authorityCaller);
23633
+ if (!authority || authority.kind !== "reconciliation")
23634
+ return failure("permission_denied", "native reconciliation authority is not verified");
23635
+ if (authority.taskId !== input.taskId || authority.decisionId !== input.decisionId || !sameRef(authority.actionRef, input.actionRef) || authority.taskRevision !== input.taskRevision || authority.workspaceRevision !== input.workspaceRevision || authority.outcome !== input.outcome || authority.evidenceDigest !== input.evidenceDigest || authority.step !== input.step)
23636
+ return failure("permission_denied", "native reconciliation authority is not bound");
23637
+ const changed = input.store.mutateTask(input.taskId, input.taskRevision, (current, mutation) => {
23638
+ const workspace = input.store.readWorkspace();
23639
+ if (!workspace.ok)
23640
+ return workspace;
23641
+ if (!workspace.data)
23642
+ return failure("not_found", "workspace not found");
23643
+ if (authority.workspaceId !== workspace.data.id)
23644
+ return failure("permission_denied", "native reconciliation workspace does not match");
23645
+ if (workspace.data.revision !== input.workspaceRevision)
23646
+ return failure("revision_conflict", "workspace revision does not match");
23647
+ const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
23648
+ if (!entry || entry.data.purpose !== "action")
23649
+ return failure("not_found", "decision not found");
23650
+ if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
23651
+ return failure("permission_denied", "native reconciliation caller does not match approval");
23652
+ if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
23653
+ return failure("permission_denied", "reconciliation decision binding is invalid");
23654
+ if (entry.data.consumption?.state !== "uncertain")
23655
+ return failure("external_outcome_unknown", "only an uncertain action can be reconciled");
23656
+ if (!sameRef(entry.data.consumption.actionRef, input.actionRef))
23657
+ return failure("permission_denied", "reconciliation reference does not match reservation");
23658
+ const stored = current.actionProgress?.find((progress) => progress.decisionId === input.decisionId);
23659
+ const workflow = stored ? { steps: [...stored.steps], completed: [...stored.completedSteps] } : { steps: [], completed: [] };
23660
+ const currentStep = input.step ?? workflow.steps[workflow.completed.length];
23661
+ if (workflow.steps.length && (!currentStep || currentStep !== workflow.steps[workflow.completed.length]))
23662
+ return failure("permission_denied", "reconciliation step is outside the approved workflow");
23663
+ if (input.outcome === "not_started") {
23664
+ return success(mutation.revision, null, {
23665
+ ...current,
23666
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: { ...candidate.data, consumption: null } } : candidate)
23667
+ });
23539
23668
  }
23540
- }
23541
- const pathArgs = paths.length ? ["--", ...paths] : [];
23542
- const diff_stat = runHere(["diff", "--stat", ...pathArgs]);
23543
- const cached_stat = runHere(["diff", "--cached", "--stat", ...pathArgs]);
23544
- const stagedSet = new Set(staged);
23545
- const unstagedSet = new Set(unstaged);
23546
- const partial_staged = [...stagedSet].filter((f) => unstagedSet.has(f));
23547
- return {
23548
- workspace_root: cwd,
23549
- branch,
23550
- status_short,
23551
- staged,
23552
- unstaged,
23553
- untracked,
23554
- diff_stat: [diff_stat, cached_stat].filter(Boolean).join(`
23555
- `),
23556
- partial_staged: partial_staged.length > 0,
23557
- partial_staged_files: partial_staged,
23558
- ...failure ? { stderr: failure.stderr, exitCode: failure.exitCode } : {}
23559
- };
23560
- };
23561
-
23562
- // packages/workit-core/src/core/config.ts
23563
- import {
23564
- copyFileSync,
23565
- cpSync,
23566
- existsSync as existsSync4,
23567
- mkdirSync as mkdirSync2,
23568
- readFileSync as readFileSync4,
23569
- readdirSync as readdirSync3,
23570
- writeFileSync as writeFileSync2
23571
- } from "node:fs";
23572
- import os from "node:os";
23573
- import path7 from "node:path";
23574
-
23575
- // packages/workit-core/src/core/boundary.ts
23576
- import { statSync } from "node:fs";
23577
- var EVENT = {
23578
- initialization: "initialization",
23579
- provenance: "provenance",
23580
- assets: "assets",
23581
- configurationSource: "configuration_source",
23582
- hooks: "hooks",
23583
- mcpConnection: "mcp_connection",
23584
- migration: "migration",
23585
- installSteps: "install_steps",
23586
- uncaughtFailure: "uncaught_failure",
23587
- toolsFailed: "tools_failed",
23588
- doctor: "doctor"
23589
- };
23590
- var errorDetail = (err) => {
23591
- if (err instanceof Error) {
23592
- return { error_name: err.name, error: err.message };
23593
- }
23594
- return { error_name: "unknown", error: String(err) };
23595
- };
23596
- var markSourcesLoaded = (files, loadedAt = Date.now()) => ({
23597
- loadedAt,
23598
- files: [...files]
23599
- });
23600
- var changedSourcesSinceLoad = (marker) => {
23601
- const changed = [];
23602
- for (const file of marker.files) {
23603
- try {
23604
- if (statSync(file).mtimeMs > marker.loadedAt)
23605
- changed.push(file);
23606
- } catch {}
23607
- }
23608
- return changed;
23609
- };
23669
+ if (currentStep)
23670
+ workflow.completed.push(currentStep);
23671
+ const complete = !workflow.steps.length || workflow.completed.length >= workflow.steps.length;
23672
+ const nextDecision = {
23673
+ ...entry.data,
23674
+ consumption: complete ? { ...entry.data.consumption, state: "consumed", at: mutation.now } : null
23675
+ };
23676
+ const actionProgress = workflow.steps.length ? [
23677
+ ...(current.actionProgress ?? []).filter((progress) => progress.decisionId !== input.decisionId),
23678
+ {
23679
+ decisionId: input.decisionId,
23680
+ steps: workflow.steps,
23681
+ completedSteps: workflow.completed
23682
+ }
23683
+ ] : current.actionProgress ?? [];
23684
+ return success(mutation.revision, null, {
23685
+ ...current,
23686
+ actionProgress,
23687
+ decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? { ...candidate, data: nextDecision } : candidate)
23688
+ });
23689
+ }, input.native?.now);
23690
+ if (!changed.ok)
23691
+ return changed;
23692
+ const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
23693
+ return entry ? success(changed.data.revision, input.workspaceRevision, entry) : failure("recovery_required", "reconciled decision disappeared");
23694
+ }
23695
+ var bindingCovers = scopeCovers;
23696
+ var verifyDecisionContent = verifyContentRefs;
23610
23697
 
23611
- // packages/workit-core/src/core/config.ts
23612
- var diagnosticLogger;
23613
- var PRESETS = {
23614
- gitflow: {
23615
- allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
23616
- protected: ["main", "develop", "master", "prod", "production"]
23617
- },
23618
- "github-flow": { allowed: ["*"], protected: ["main"] },
23619
- "trunk-based": { allowed: ["*"], protected: ["main"] },
23620
- custom: { allowed: [], protected: [] }
23621
- };
23622
- var mergePreset = (preset, input = {}, current = {
23623
- branchPolicy: { preset, allowed: [], protected: [] }
23624
- }) => {
23625
- const defs = PRESETS[preset];
23626
- return {
23627
- preset,
23628
- allowed: preset === "custom" ? input.allowed ?? current.branchPolicy.allowed : [...defs.allowed],
23629
- protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
23630
- };
23631
- };
23632
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path7.join(process.env.XDG_CONFIG_HOME || path7.join(os.homedir(), ".config"), "workit");
23633
- var migratedDir = null;
23634
- var migrationFailed = false;
23635
- var ensureConfigDir = (dir = resolveConfigDir()) => {
23636
- if (migratedDir === dir)
23637
- return dir;
23638
- if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
23639
- migratedDir = dir;
23640
- return dir;
23641
- }
23642
- const legacy = path7.join(process.env.XDG_CONFIG_HOME || path7.join(os.homedir(), ".config"), "workflow-toolkit");
23643
- if (!existsSync4(legacy)) {
23644
- migratedDir = dir;
23645
- return dir;
23646
- }
23647
- if (!migrationFailed && existsSync4(dir)) {
23648
- migratedDir = dir;
23649
- return dir;
23650
- }
23651
- migrationFailed = false;
23652
- mkdirSync2(dir, { recursive: true });
23653
- diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
23654
- for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
23655
- const src = path7.join(legacy, entry.name);
23656
- const dest = path7.join(dir, entry.name);
23657
- if (existsSync4(dest))
23658
- continue;
23659
- try {
23660
- if (entry.isDirectory())
23661
- cpSync(src, dest, { recursive: true });
23662
- else if (entry.isFile())
23663
- copyFileSync(src, dest);
23664
- } catch (err) {
23665
- migrationFailed = true;
23666
- diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
23667
- console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
23668
- }
23669
- }
23670
- if (!migrationFailed)
23671
- migratedDir = dir;
23672
- return dir;
23673
- };
23674
- var configDir = () => ensureConfigDir();
23675
- var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
23676
- var DEFAULTS = {
23677
- locale: "en",
23678
- localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
23679
- timezone: "America/Santiago",
23680
- branchPolicy: {
23681
- preset: "gitflow",
23682
- allowed: [...PRESETS.gitflow.allowed],
23683
- protected: [...PRESETS.gitflow.protected]
23684
- },
23685
- commitPolicy: { preset: "conventional" }
23686
- };
23687
- var readSafe = (p) => {
23688
- try {
23689
- return readFileSync4(p, "utf8");
23690
- } catch {
23698
+ // packages/workit-core/src/core/task-evaluation.ts
23699
+ var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
23700
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
23701
+ var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
23702
+ var canonicalPath = (value) => {
23703
+ if (typeof value !== "string" || !value)
23704
+ return null;
23705
+ if (value.includes("\\") || value.split("/").includes(".."))
23706
+ return null;
23707
+ const normalized = path9.posix.normalize(value);
23708
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
23691
23709
  return null;
23692
- }
23710
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
23693
23711
  };
23694
- var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
23695
- var parseConfigResult = (raw, file) => {
23696
- if (raw === null)
23697
- return { status: "missing", path: file };
23698
- let parsed;
23699
- try {
23700
- parsed = JSON.parse(raw);
23701
- } catch {
23702
- return { status: "malformed", path: file, error: `${file} is not valid JSON` };
23703
- }
23704
- if (!isConfigObject(parsed)) {
23705
- return { status: "malformed", path: file, error: `${file} is not a JSON object` };
23706
- }
23707
- const input = parsed;
23708
- const locale = LOCALE_RE.test(String(input.locale ?? "")) ? input.locale : DEFAULTS.locale;
23709
- const preset = Object.hasOwn(PRESETS, input.branchPolicy?.preset) ? input.branchPolicy?.preset : "gitflow";
23710
- const commitPreset = COMMIT_PRESETS.includes(String(input.commitPolicy?.preset ?? "")) ? input.commitPolicy?.preset : "conventional";
23712
+ var canonicalScope = (scope) => {
23713
+ const paths = scope.paths.length ? scope.paths : ["."];
23714
+ const normalizedPaths = paths.map(canonicalPath);
23715
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
23716
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
23717
+ return null;
23711
23718
  return {
23712
- status: "valid",
23713
- path: file,
23714
- config: {
23715
- locale,
23716
- localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
23717
- timezone: input.timezone ?? DEFAULTS.timezone,
23718
- branchPolicy: mergePreset(preset, {
23719
- allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
23720
- protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
23721
- }, DEFAULTS),
23722
- commitPolicy: {
23723
- preset: commitPreset,
23724
- ...typeof input.commitPolicy?.pattern === "string" ? { pattern: input.commitPolicy.pattern } : {}
23725
- }
23726
- }
23719
+ description: scope.description,
23720
+ paths: [...new Set(normalizedPaths)],
23721
+ exclusions: [...new Set(normalizedExclusions)]
23727
23722
  };
23728
23723
  };
23729
- var readConfigTyped = (dir) => {
23730
- const file = path7.join(dir ?? configDir(), "config.json");
23731
- return parseConfigResult(readSafe(file), file);
23732
- };
23733
- var readConfig = () => {
23734
- const result = readConfigTyped();
23735
- if (result.status === "malformed")
23736
- throw new Error(result.error);
23737
- return result.config ?? DEFAULTS;
23724
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
23725
+ var scopeMatches = (value, scope) => {
23726
+ const normalized = canonicalScope(scope);
23727
+ if (!normalized)
23728
+ return false;
23729
+ const paths = normalized.paths;
23730
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
23738
23731
  };
23739
- var resolveBranchPolicy = (config, workspace) => {
23740
- const wp = workspace?.branchPolicy ?? {};
23741
- const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
23742
- const merged = mergePreset(preset, {
23743
- allowed: wp.allowed,
23744
- protectedNames: wp.protected
23745
- }, config);
23746
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
23747
- return {
23748
- preset,
23749
- allowed,
23750
- protected: new Set(merged.protected.map((p) => p.toLowerCase())),
23751
- integration: wp.integration === "merge" ? "merge" : "pr",
23752
- defaultTargetBranch: preset === "github-flow" ? "main" : preset === "trunk-based" ? "master" : "develop"
23753
- };
23732
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
23733
+ var compareCodeUnits2 = (left, right) => {
23734
+ const length = Math.min(left.length, right.length);
23735
+ for (let index = 0;index < length; index += 1) {
23736
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
23737
+ if (difference !== 0)
23738
+ return difference;
23739
+ }
23740
+ return left.length - right.length;
23754
23741
  };
23755
- var COMMIT_PRESETS = [
23756
- "conventional",
23757
- "gitmoji",
23758
- "ticket-prefix",
23759
- "freeform",
23760
- "custom",
23761
- "auto"
23762
- ];
23763
-
23764
- // packages/workit-core/src/core/workspaces.ts
23765
- import { readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
23766
- import path8 from "node:path";
23767
- var readWorkspacesResult = (dir = configDir()) => {
23768
- const file = path8.join(dir, "workspaces.json");
23769
- let raw;
23770
- try {
23771
- raw = readFileSync5(file, "utf8");
23772
- } catch {
23773
- return { status: "missing", path: file, entries: [] };
23742
+ var gitPaths = (root) => {
23743
+ const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
23744
+ cwd: root,
23745
+ encoding: "buffer"
23746
+ });
23747
+ if (result.status !== 0) {
23748
+ const stderr = result.stderr?.toString("utf8") ?? "";
23749
+ return {
23750
+ paths: [],
23751
+ uncertain: !/not a git repository/i.test(stderr),
23752
+ git: /not a git repository/i.test(stderr) ? false : true
23753
+ };
23774
23754
  }
23775
- let parsed;
23755
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
23756
+ const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
23757
+ if (stagedDeleted.status !== 0) {
23758
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
23759
+ if (!/not a git repository/i.test(stderr))
23760
+ return { paths, uncertain: true, git: true };
23761
+ } else if (stagedDeleted.stdout) {
23762
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
23763
+ }
23764
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
23765
+ };
23766
+ var walk = (root, directory, output) => {
23767
+ let names;
23776
23768
  try {
23777
- parsed = JSON.parse(raw);
23769
+ names = fs7.readdirSync(directory);
23778
23770
  } catch {
23779
- return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
23771
+ return true;
23780
23772
  }
23781
- if (!isConfigObject(parsed)) {
23782
- return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
23773
+ let uncertain = false;
23774
+ for (const name of names) {
23775
+ if (name === ".git" || name === ".workit")
23776
+ continue;
23777
+ const target = path9.join(directory, name);
23778
+ let stat;
23779
+ try {
23780
+ stat = fs7.lstatSync(target);
23781
+ } catch {
23782
+ uncertain = true;
23783
+ continue;
23784
+ }
23785
+ const item = relative(root, target);
23786
+ output.add(item);
23787
+ if (stat.isDirectory() && !stat.isSymbolicLink())
23788
+ uncertain = walk(root, target, output) || uncertain;
23783
23789
  }
23784
- const list = parsed.workspaces;
23785
- return {
23786
- status: "valid",
23787
- path: file,
23788
- entries: Array.isArray(list) ? list : []
23789
- };
23790
+ return uncertain;
23790
23791
  };
23791
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
23792
- var globToRegExp = (glob) => {
23793
- let out = "";
23794
- for (let i = 0;i < glob.length; i++) {
23795
- const c = glob[i];
23796
- if (c === "*") {
23797
- if (glob[i + 1] === "*") {
23798
- if (glob[i + 2] === "/") {
23799
- if (out === "")
23800
- out += "/?";
23801
- out += "(?:[^/]+/)*";
23802
- i += 2;
23803
- } else {
23804
- if (i + 2 >= glob.length) {
23805
- if (out.endsWith("/")) {
23806
- out = out.slice(0, -1);
23807
- out += "(?:/.*)?";
23808
- } else {
23809
- out += ".*";
23810
- }
23811
- } else {
23812
- out += ".*";
23813
- }
23814
- i++;
23815
- }
23816
- } else {
23817
- out += "[^/]*";
23818
- }
23819
- } else {
23820
- out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
23792
+ var scopeRoots = (root, scope) => {
23793
+ const roots = [];
23794
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
23795
+ const target = path9.resolve(root, item);
23796
+ if (!inside(root, target))
23797
+ return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
23798
+ let ancestor = target;
23799
+ while (!fs7.existsSync(ancestor) && ancestor !== root)
23800
+ ancestor = path9.dirname(ancestor);
23801
+ try {
23802
+ if (!inside(root, fs7.realpathSync(ancestor)))
23803
+ return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
23804
+ } catch {
23805
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
23821
23806
  }
23807
+ roots.push(target);
23822
23808
  }
23823
- return new RegExp(`^${out}$`);
23809
+ return success(null, null, roots);
23824
23810
  };
23825
- var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
23826
- var realpathOf = (p) => {
23827
- const real = realpathSync4.native ?? realpathSync4;
23811
+ function captureCandidate(root, scope, environment = []) {
23812
+ let checkout;
23828
23813
  try {
23829
- return real(p);
23814
+ checkout = fs7.realpathSync(root);
23830
23815
  } catch {
23831
- return p;
23816
+ return failure("invalid_input", "candidate root does not exist");
23832
23817
  }
23833
- };
23834
- var canonicalGlob = (glob) => {
23835
- const m = /[*?[\]{]/.exec(glob);
23836
- const prefix = m ? glob.slice(0, m.index) : glob;
23837
- const rest = m ? glob.slice(m.index) : "";
23838
- if (!prefix)
23839
- return glob;
23840
- const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
23841
- return real + (prefix.endsWith("/") ? "/" : "") + rest;
23842
- };
23843
- var resolveWorkspaceFrom = (cwd, dir) => {
23844
- const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
23845
- for (const entry of loadWorkspacesFrom(dir)) {
23846
- if (!entry || typeof entry !== "object")
23818
+ const normalizedScope = canonicalScope(scope);
23819
+ if (!normalizedScope)
23820
+ return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
23821
+ const roots = scopeRoots(checkout, normalizedScope);
23822
+ if (!roots.ok)
23823
+ return roots;
23824
+ const git = gitPaths(checkout);
23825
+ const names = new Set;
23826
+ let uncertain = git.uncertain;
23827
+ for (const target of roots.data) {
23828
+ const item = relative(checkout, target);
23829
+ if (item === ".git" || item === ".workit")
23847
23830
  continue;
23848
- const ws = entry;
23849
- if (typeof ws.glob !== "string" || !ws.glob)
23831
+ names.add(item);
23832
+ if (!fs7.existsSync(target))
23850
23833
  continue;
23851
- const glob = ws.glob.replaceAll("\\", "/");
23852
- const canonical = canonicalGlob(glob);
23853
- for (const target of targets) {
23854
- if (matchWorkspace(glob, target))
23855
- return ws;
23856
- if (canonical !== glob && matchWorkspace(canonical, target))
23857
- return ws;
23834
+ let stat;
23835
+ try {
23836
+ stat = fs7.lstatSync(target);
23837
+ } catch {
23838
+ uncertain = true;
23839
+ continue;
23840
+ }
23841
+ if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
23842
+ uncertain = walk(checkout, target, names) || uncertain;
23843
+ }
23844
+ for (const item of git.paths)
23845
+ if (scopeMatches(item, normalizedScope))
23846
+ names.add(item);
23847
+ const files = [];
23848
+ let completeness = uncertain ? "uncertain" : "known";
23849
+ for (const item of [...names].sort()) {
23850
+ if (!scopeMatches(item, normalizedScope))
23851
+ continue;
23852
+ const target = path9.join(checkout, item);
23853
+ try {
23854
+ const stat = fs7.lstatSync(target);
23855
+ if (stat.isSymbolicLink()) {
23856
+ const link = fs7.readlinkSync(target);
23857
+ files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
23858
+ } else if (stat.isFile()) {
23859
+ files.push({
23860
+ path: item,
23861
+ kind: "file",
23862
+ digest: digestBytes3(fs7.readFileSync(target)),
23863
+ executable: (stat.mode & 73) !== 0
23864
+ });
23865
+ } else if (!stat.isDirectory()) {
23866
+ completeness = "uncertain";
23867
+ } else if (git.git && git.paths.includes(item)) {
23868
+ completeness = "uncertain";
23869
+ }
23870
+ } catch (error) {
23871
+ if (error?.code === "ENOENT") {
23872
+ files.push({ path: item, kind: "absent", digest: null, executable: null });
23873
+ } else
23874
+ completeness = "uncertain";
23858
23875
  }
23859
23876
  }
23860
- return null;
23877
+ const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
23878
+ name,
23879
+ value: environment[name] ?? null
23880
+ }));
23881
+ const namesByEnvironment = supplied.map(({ name }) => name);
23882
+ if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
23883
+ return failure("invalid_input", "candidate environment names must be unique and non-empty");
23884
+ const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
23885
+ if (values.some(({ value }) => value === null))
23886
+ completeness = "uncertain";
23887
+ const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
23888
+ if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
23889
+ completeness = "uncertain";
23890
+ const head = headResult.status === 0 ? headResult.stdout.trim() : null;
23891
+ const partial = {
23892
+ id: "0".repeat(64),
23893
+ scope: normalizedScope,
23894
+ completeness,
23895
+ files,
23896
+ environment: values,
23897
+ head
23898
+ };
23899
+ const candidate = { ...partial, id: candidateDigest(partial) };
23900
+ const parsed = candidateSchema.safeParse(candidate);
23901
+ return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
23902
+ }
23903
+ var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
23904
+ var changedPaths = (before, after) => {
23905
+ const paths = new Set([...before.files, ...after.files].map((file) => file.path));
23906
+ const left = fileMap(before);
23907
+ const right = fileMap(after);
23908
+ const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
23909
+ if (before.head !== after.head)
23910
+ changed.push(".");
23911
+ if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
23912
+ changed.push(".");
23913
+ return changed;
23861
23914
  };
23862
- var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
23863
-
23864
- // packages/workit-core/src/core/vcs-config.ts
23865
- import fs7 from "node:fs";
23866
- import path9 from "node:path";
23867
- import { spawnSync as spawnSync2 } from "node:child_process";
23868
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
23869
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
23870
- var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
23871
- var remoteProvider = (cwd) => {
23872
- const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
23873
- if (r.status !== 0)
23915
+ var sessionFromRef = (value) => {
23916
+ if (typeof value !== "object" || value === null || value.kind !== "host")
23874
23917
  return null;
23875
- const url = (r.stdout ?? "").trim();
23876
- if (/github\.com[:/]/.test(url))
23877
- return "github";
23878
- if (/gitlab\.com[:/]/.test(url))
23879
- return "gitlab";
23880
- return null;
23918
+ return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
23881
23919
  };
23882
- var readVcsConfig = () => {
23883
- const file = vcsConfigPath();
23884
- let raw;
23885
- try {
23886
- raw = fs7.readFileSync(file, "utf8");
23887
- } catch {
23888
- return { status: "missing", path: file, config: {} };
23889
- }
23890
- let parsed;
23891
- try {
23892
- parsed = JSON.parse(raw);
23893
- } catch {
23894
- return { status: "malformed", path: file, config: {}, error: `${file} is not valid JSON` };
23895
- }
23896
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
23897
- return { status: "valid", path: file, config: parsed };
23898
- }
23899
- return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
23920
+ var sameSession = (left, right) => {
23921
+ const other = sessionFromRef(right);
23922
+ return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
23900
23923
  };
23901
- function vcsConfig(mode, cwd) {
23902
- const ws = resolveWorkspace(vcsCwd(cwd));
23903
- const wsVcs = ws?.vcs ?? {};
23904
- const wsYt = ws?.youtrack ?? {};
23905
- const wsIssues = ws?.issues ?? {};
23906
- const { status: cfgStatus, path: cfgPath, config: cfg, error: cfgError } = readVcsConfig();
23907
- const root = vcsCwd(cwd);
23908
- const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
23909
- const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
23910
- const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
23911
- const wp = ws?.branchPolicy ?? {};
23912
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
23913
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
23914
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
23915
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
23916
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
23917
- let issuesProvider = null;
23918
- let linkOnPr = null;
23919
- if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
23920
- issuesProvider = "github";
23921
- linkOnPr = typeof wsIssues.link_on_pr === "boolean" ? wsIssues.link_on_pr : null;
23922
- }
23923
- if (mode === "resolve") {
23924
- if (cfgStatus === "malformed")
23925
- return { ok: false, error: cfgError, configPath: cfgPath };
23924
+ var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
23925
+ function evaluateEvidence(task, candidate) {
23926
+ const current = candidate ?? task.candidates.at(-1) ?? null;
23927
+ return task.evidence.map((entry) => {
23928
+ const evidence = entry.data;
23929
+ if (evidence.result === "failed")
23930
+ return {
23931
+ evidenceId: entry.id,
23932
+ status: "failed",
23933
+ reason: "failed evidence remains historical"
23934
+ };
23935
+ if (evidence.kind === "check" || evidence.kind === "review") {
23936
+ if (!evidence.beforeCandidateId || !evidence.candidateId)
23937
+ return {
23938
+ evidenceId: entry.id,
23939
+ status: "stale",
23940
+ reason: "check or review evidence lacks start and end candidates"
23941
+ };
23942
+ if (evidence.beforeCandidateId !== evidence.candidateId)
23943
+ return {
23944
+ evidenceId: entry.id,
23945
+ status: "stale",
23946
+ reason: "candidate changed during the check or review"
23947
+ };
23948
+ }
23949
+ if (!evidence.candidateId || !current)
23950
+ return {
23951
+ evidenceId: entry.id,
23952
+ status: evidence.result,
23953
+ reason: "evidence has no candidate binding"
23954
+ };
23955
+ const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
23956
+ if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
23957
+ return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
23958
+ if (observed.id === current.id)
23959
+ return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
23960
+ const scopes = evidenceScopes(task, evidence.requirementIds);
23961
+ const changed = changedPaths(observed, current);
23962
+ const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
23963
+ if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
23964
+ return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
23926
23965
  return {
23927
- ok: true,
23928
- workspace_name: ws?.name ?? null,
23929
- provider,
23930
- defaultTargetBranch: defaultTarget,
23931
- link_issues: linkIssues,
23932
- youtrack_base_url: youtrackBaseUrl,
23933
- issues_provider: issuesProvider,
23934
- link_on_pr: linkOnPr
23966
+ evidenceId: entry.id,
23967
+ status: evidence.result,
23968
+ reason: "candidate changed outside evidence scope"
23935
23969
  };
23936
- }
23937
- if (cfgStatus === "malformed") {
23938
- return { ok: false, error: cfgError, configPath: cfgPath };
23939
- }
23940
- if (cfgStatus === "missing") {
23941
- return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
23942
- }
23943
- if (provider === null) {
23970
+ });
23971
+ }
23972
+ var scopeCovers2 = scopeCovers;
23973
+ var checkPin = (assignment, beforeCandidateId, candidateId) => {
23974
+ const pin = assignment.candidateId ?? null;
23975
+ if (pin === null)
23976
+ return true;
23977
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
23978
+ };
23979
+ var resolveBinding = (evidence, pin, currentCandidateId) => {
23980
+ const bound = evidence.kind === "check" || evidence.kind === "review";
23981
+ const fallback = bound ? pin ?? currentCandidateId : null;
23982
+ return {
23983
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
23984
+ candidateId: evidence.candidateId ?? fallback
23985
+ };
23986
+ };
23987
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
23988
+ var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
23989
+ var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
23990
+ var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
23991
+ if (kind !== "passed")
23992
+ return false;
23993
+ if (dimension === "testing" || dimension === "verification")
23994
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
23995
+ if (dimension === "review")
23996
+ return evidenceKind === "review";
23997
+ if (dimension === "investigation" || dimension === "challenge")
23998
+ return evidenceKind === "investigation";
23999
+ if (dimension === "artifacts" || dimension === "continuity")
24000
+ return evidenceKind === "artifact";
24001
+ return false;
24002
+ };
24003
+ var expectedKinds = (dimension, ruleId) => {
24004
+ if (dimension === "testing")
24005
+ return "check";
24006
+ if (dimension === "verification")
24007
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
24008
+ if (dimension === "review")
24009
+ return "review";
24010
+ if (dimension === "investigation" || dimension === "challenge")
24011
+ return "investigation";
24012
+ if (dimension === "artifacts" || dimension === "continuity")
24013
+ return "artifact";
24014
+ if (dimension === "decisions")
24015
+ return "decision";
24016
+ return "worker report";
24017
+ };
24018
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
24019
+ const evidence = evaluateEvidence(task, candidate);
24020
+ if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
24021
+ return task.policy.requirements.map((requirement) => ({
24022
+ requirementId: requirement.id,
24023
+ status: "unsatisfied",
24024
+ evidenceIds: [],
24025
+ decisionIds: [],
24026
+ reason: "stored policy version is unsupported; reassessment is required"
24027
+ }));
24028
+ return (task.policy?.requirements ?? []).map((requirement) => {
24029
+ const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
24030
+ const passed = related.filter(({ entry, evaluation }) => {
24031
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
24032
+ return false;
24033
+ if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
24034
+ return false;
24035
+ if (requirement.dimension !== "review")
24036
+ return true;
24037
+ const reviewSession = sessionFromRef(entry.data.reviewContext);
24038
+ const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
24039
+ const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
24040
+ return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
24041
+ });
24042
+ if (passed.length) {
24043
+ const currentId = candidate?.id ?? null;
24044
+ const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
24045
+ if (withRed.length)
24046
+ return {
24047
+ requirementId: requirement.id,
24048
+ status: "satisfied",
24049
+ evidenceIds: withRed.map(({ entry }) => entry.id),
24050
+ decisionIds: [],
24051
+ reason: "fresh applicable evidence passed"
24052
+ };
24053
+ if (requirement.dimension === "testing")
24054
+ return {
24055
+ requirementId: requirement.id,
24056
+ status: "unsatisfied",
24057
+ evidenceIds: [],
24058
+ decisionIds: [],
24059
+ reason: "testing requirement has GREEN evidence but no preceding RED"
24060
+ };
24061
+ }
24062
+ if (requirement.dimension === "delegation") {
24063
+ const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
24064
+ const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
24065
+ if (reconciled)
24066
+ return {
24067
+ requirementId: requirement.id,
24068
+ status: "satisfied",
24069
+ evidenceIds: [],
24070
+ decisionIds: [],
24071
+ reason: "bounded helper completed and its result is recorded"
24072
+ };
24073
+ }
24074
+ const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
24075
+ if (decisions.length)
24076
+ return {
24077
+ requirementId: requirement.id,
24078
+ status: "satisfied",
24079
+ evidenceIds: [],
24080
+ decisionIds: decisions.map((decision) => decision.id),
24081
+ reason: "an applicable approved or stated decision satisfies the requirement"
24082
+ };
24083
+ const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
24084
+ if (limitations.length)
24085
+ return {
24086
+ requirementId: requirement.id,
24087
+ status: "accepted_limitation",
24088
+ evidenceIds: [],
24089
+ decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
24090
+ reason: "an applicable approved limitation permits the missing evidence"
24091
+ };
24092
+ const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
24093
+ if (!unavailable && related.length > 0 && !passed.length)
24094
+ return {
24095
+ requirementId: requirement.id,
24096
+ status: "unsatisfied",
24097
+ evidenceIds: related.map(({ entry }) => entry.id),
24098
+ decisionIds: [],
24099
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
24100
+ };
23944
24101
  return {
23945
- ok: false,
23946
- error: `no vcs provider configured (set vcs.provider in ${cfgPath}, match a workspace entry, or run inside a checkout with a recognized origin remote)`,
23947
- configPath: cfgPath
23948
- };
23949
- }
23950
- const prov = cfg[provider] ?? {};
23951
- const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
23952
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path9.join(configDir(), `${provider}.token`));
23953
- const tokenPath = path9.resolve(tokenFile);
23954
- let tokenOk = false;
23955
- if (fs7.existsSync(tokenPath)) {
23956
- const token = fs7.readFileSync(tokenPath, "utf8").trim();
23957
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
23958
- tokenOk = !placeholder;
23959
- }
23960
- const out = {
23961
- ok: true,
23962
- configPath: path9.resolve(cfgPath),
23963
- provider,
23964
- defaultTargetBranch: defaultTarget,
23965
- pr: cfg.pr ?? {},
23966
- tokenPath,
23967
- tokenPresent: fs7.existsSync(tokenPath),
23968
- tokenReady: tokenOk,
23969
- workspace_name: ws?.name ?? null,
23970
- link_issues: linkIssues,
23971
- youtrack_base_url: youtrackBaseUrl,
23972
- issues_provider: issuesProvider,
23973
- link_on_pr: linkOnPr
23974
- };
23975
- if (provider === "gitlab") {
23976
- out.gitlab = {
23977
- host: prov.host ?? "gitlab.com",
23978
- apiUrl: prov.apiUrl ?? "https://gitlab.com/api/v4"
24102
+ requirementId: requirement.id,
24103
+ status: unavailable ? "unavailable" : "unsatisfied",
24104
+ evidenceIds: related.map(({ entry }) => entry.id),
24105
+ decisionIds: [],
24106
+ reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
23979
24107
  };
23980
- } else if (provider === "github") {
23981
- out.github = { host: prov.host ?? "github.com" };
23982
- }
23983
- if (mode === "summary")
23984
- delete out.tokenPath;
23985
- return out;
23986
- }
23987
- function mergedPrStyle(limit = 6, cwd) {
23988
- const cfg = vcsConfig("load", cwd);
23989
- if (!cfg.ok || !cfg.tokenReady)
23990
- return { ok: false, error: "vcs not configured" };
23991
- const provider = cfg.provider;
23992
- const token = fs7.readFileSync(cfg.tokenPath, "utf8").trim();
23993
- const examples = [];
23994
- const descInfo = (desc, caseInsensitiveNotes = false) => ({
23995
- hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
23996
- sections: desc.split(`
23997
- `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
23998
- descriptionPreview: desc.slice(0, 600)
23999
24108
  });
24000
- if (provider === "gitlab") {
24001
- const remote = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
24002
- if (remote.status !== 0)
24003
- return { ok: false, error: "no origin remote" };
24004
- const url = (remote.stdout ?? "").trim();
24005
- const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
24006
- if (!m)
24007
- return { ok: false, error: "not a gitlab.com origin" };
24008
- const project = m[1];
24009
- const env = { ...process.env, GITLAB_TOKEN: token };
24010
- const run = (args) => spawnSync2("glab", ["api", ...args], { cwd, encoding: "utf8", env });
24011
- let r = run([
24012
- `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
24013
- ]);
24014
- if (r.status !== 0) {
24015
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
24016
- }
24017
- if (r.status !== 0)
24018
- return { ok: false, error: "could not list merge requests" };
24019
- for (const mr of JSON.parse(r.stdout ?? "[]")) {
24020
- const desc = String(mr.description ?? "").trim();
24021
- examples.push({
24022
- title: mr.title,
24023
- url: mr.web_url,
24024
- squash: mr.squash,
24025
- ...descInfo(desc, true)
24026
- });
24027
- }
24028
- } else if (provider === "github") {
24029
- const r = spawnSync2("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
24030
- if (r.status !== 0)
24031
- return { ok: false, error: "could not list pull requests" };
24032
- for (const pr of JSON.parse(r.stdout ?? "[]")) {
24033
- const desc = String(pr.body ?? "").trim();
24034
- examples.push({ title: pr.title, url: pr.url, ...descInfo(desc) });
24109
+ }
24110
+ function evaluateClosure(requestedOutcome, view) {
24111
+ if (requestedOutcome !== "stopped" && !view.task.policy)
24112
+ return failure("requirements_unsatisfied", "task has not been assessed");
24113
+ if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
24114
+ return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
24115
+ const evaluations = view.requirements;
24116
+ const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
24117
+ if (requestedOutcome !== "stopped" && openFindings.length)
24118
+ return failure("requirements_unsatisfied", "open findings must be resolved before closure");
24119
+ if (requestedOutcome !== "stopped") {
24120
+ const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
24121
+ for (const entry of view.task.findings) {
24122
+ if (entry.data.disposition === "fixed") {
24123
+ const verified = entry.data.resolution?.evidenceIds.some((id) => {
24124
+ const evidence = view.task.evidence.find((item) => item.id === id);
24125
+ if (!evidence)
24126
+ return false;
24127
+ return findingVerificationPasses(entry.data.candidateId, {
24128
+ kind: evidence.data.kind,
24129
+ candidateId: evidence.data.candidateId,
24130
+ status: evidenceById.get(id) ?? "missing"
24131
+ });
24132
+ });
24133
+ if (!verified)
24134
+ return failure("requirements_unsatisfied", "fixed findings require current verification");
24135
+ }
24136
+ if (entry.data.disposition === "deferred") {
24137
+ const valid = entry.data.resolution?.decisionIds.some((id) => {
24138
+ const decisionEntry = view.task.decisions.find((item) => item.id === id);
24139
+ const decision = decisionEntry?.data;
24140
+ return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
24141
+ });
24142
+ if (!valid)
24143
+ return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
24144
+ }
24035
24145
  }
24036
24146
  }
24037
- return {
24038
- ok: true,
24039
- provider,
24040
- count: examples.length,
24041
- styleHints: [
24042
- "Prefer ## Summary bullets + ## Validation or ## Test plan only",
24043
- "Do not add ## Notes with branch names, commit counts, or diff stats",
24044
- "Do not paste commit log or diff stat into the body"
24045
- ],
24046
- examples
24047
- };
24147
+ const blocking = evaluations.filter((item) => {
24148
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
24149
+ return false;
24150
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
24151
+ return !requirement || requirement.before === "close";
24152
+ });
24153
+ if (requestedOutcome !== "stopped" && blocking.length)
24154
+ return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
24155
+ requirementIds: blocking.map((item) => item.requirementId),
24156
+ requirements: blocking.map((item) => {
24157
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
24158
+ return {
24159
+ requirementId: item.requirementId,
24160
+ ruleId: requirement?.ruleId ?? "unknown",
24161
+ reason: item.reason,
24162
+ satisfaction: requirement?.satisfaction ?? "",
24163
+ dependentAction: requirement?.dependentAction ?? null
24164
+ };
24165
+ })
24166
+ });
24167
+ const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
24168
+ if (requestedOutcome === "verified" && accepted.length)
24169
+ return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
24170
+ requirementIds: accepted.map((item) => item.requirementId)
24171
+ });
24172
+ const evidenceIds = [
24173
+ ...new Set([
24174
+ ...evaluations.flatMap((item) => item.evidenceIds),
24175
+ ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
24176
+ ])
24177
+ ];
24178
+ const decisionIds = [
24179
+ ...new Set([
24180
+ ...evaluations.flatMap((item) => item.decisionIds),
24181
+ ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
24182
+ ])
24183
+ ];
24184
+ return success(null, null, {
24185
+ outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
24186
+ evidenceIds,
24187
+ decisionIds,
24188
+ requirementIds: evaluations.map((item) => item.requirementId)
24189
+ });
24048
24190
  }
24049
24191
 
24050
- // packages/workit-core/src/core/branch.ts
24051
- var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
24192
+ // packages/workit-core/src/core/task-context.ts
24193
+ function reconcileResume(view, observations = []) {
24194
+ if (observations.length > 0)
24195
+ return failure("permission_denied", "worker observations require native host verification");
24196
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
24197
+ if (!candidate.ok)
24198
+ return candidate;
24199
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
24200
+ const blockers = [...view.task.progress.blockers];
24201
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
24202
+ blockers.push({
24203
+ reason: "worker state requires reconciliation",
24204
+ dependentAction: "resume",
24205
+ refs: []
24206
+ });
24207
+ return success(null, null, {
24208
+ candidate: candidate.data,
24209
+ staleEvidenceIds,
24210
+ workerUpdates: [],
24211
+ blockers,
24212
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
24213
+ });
24214
+ }
24215
+ var exportDigest = (bundle) => sha256({
24216
+ schemaVersion: bundle.schemaVersion,
24217
+ exportedAt: bundle.exportedAt,
24218
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
24219
+ task: bundle.task
24220
+ });
24052
24221
  // packages/workit-core/src/core/external-action.ts
24053
24222
  var externalActionSchema = discriminatedUnion2("operation", [
24054
24223
  object4({
@@ -24082,6 +24251,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24082
24251
  babysit: boolean5().optional()
24083
24252
  }).strict()
24084
24253
  }).strict(),
24254
+ object4({
24255
+ operation: literal3("hosting.merge"),
24256
+ payload: object4({
24257
+ target_branch: string5().min(1).optional(),
24258
+ source_branch: string5().min(1).optional()
24259
+ }).strict()
24260
+ }).strict(),
24085
24261
  object4({
24086
24262
  operation: literal3("youtrack.update"),
24087
24263
  payload: object4({
@@ -25195,7 +25371,10 @@ class WorkitCore {
25195
25371
  observeDecision(request, observation) {
25196
25372
  return this.recordDecision(request, observation, true);
25197
25373
  }
25198
- recordDecision(request, nativeObservation, nativeRequired = false) {
25374
+ observeStandingDecision(request) {
25375
+ return this.recordDecision(request, undefined, true, true);
25376
+ }
25377
+ recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
25199
25378
  const root = this.contextRootError();
25200
25379
  if (!root.ok)
25201
25380
  return root;
@@ -25210,8 +25389,10 @@ class WorkitCore {
25210
25389
  return failure("permission_denied", "stated choices cannot authorize mutating actions");
25211
25390
  if (stated && !input.binding?.statedChoice)
25212
25391
  return failure("invalid_input", "stated choices require binding.statedChoice");
25213
- if (nativeRequired && nativeObservation === undefined && !stated)
25392
+ if (nativeRequired && nativeObservation === undefined && !stated && !standing)
25214
25393
  return failure("permission_denied", "native decision observation is required");
25394
+ if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
25395
+ return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
25215
25396
  const task = this.store.readTask(input.taskId);
25216
25397
  if (!task.ok)
25217
25398
  return task;
@@ -25243,7 +25424,10 @@ class WorkitCore {
25243
25424
  consumption: null
25244
25425
  };
25245
25426
  const data = { ...base, digest: decisionDigest(base) };
25246
- const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
25427
+ const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
25428
+ if (standingApproval && !standingApproval.ok)
25429
+ return standingApproval;
25430
+ const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
25247
25431
  observation: nativeObservation,
25248
25432
  expected: {
25249
25433
  taskId: task.data.id,
@@ -25274,7 +25458,7 @@ class WorkitCore {
25274
25458
  const entry = {
25275
25459
  id: newId(),
25276
25460
  recordedAt: mutation.now,
25277
- provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
25461
+ provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
25278
25462
  data
25279
25463
  };
25280
25464
  return success(mutation.revision, null, {
@@ -26493,7 +26677,7 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
26493
26677
 
26494
26678
  // packages/workit-core/src/core/repo-context.ts
26495
26679
  import { spawnSync as spawnSync4 } from "node:child_process";
26496
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26680
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26497
26681
  import path12 from "node:path";
26498
26682
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26499
26683
  try {
@@ -26663,7 +26847,7 @@ var packageScripts = (cwd, keys) => {
26663
26847
  if (!existsSync6(pkgPath))
26664
26848
  return [];
26665
26849
  try {
26666
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
26850
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
26667
26851
  return keys.filter((k) => pkg.scripts && typeof pkg.scripts[k] === "string").map((k) => `${k}: ${pkg.scripts?.[k]}`);
26668
26852
  } catch {
26669
26853
  return [];
@@ -26697,7 +26881,7 @@ var documentationFiles = (cwd) => {
26697
26881
  var readTrimmed = (file, maxLines) => {
26698
26882
  if (!existsSync6(file))
26699
26883
  return "";
26700
- const lines = readFileSync6(file, "utf8").split(`
26884
+ const lines = readFileSync7(file, "utf8").split(`
26701
26885
  `);
26702
26886
  return lines.slice(0, maxLines).join(`
26703
26887
  `);
@@ -26885,7 +27069,7 @@ function docsRefreshContext(root, range) {
26885
27069
  const pkgPath = path12.join(cwd, "package.json");
26886
27070
  if (existsSync6(pkgPath)) {
26887
27071
  try {
26888
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
27072
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
26889
27073
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
26890
27074
  `;
26891
27075
  } catch {}
@@ -27583,7 +27767,7 @@ var TOOL_CAPABILITIES = [
27583
27767
  ];
27584
27768
  var VERSION = (() => {
27585
27769
  try {
27586
- const packageJson = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8"));
27770
+ const packageJson = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf8"));
27587
27771
  return typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
27588
27772
  } catch {
27589
27773
  return "0.0.0";