greprag 5.74.0 → 5.74.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/codex-chip-hooks.js +2 -2
  2. package/dist/codex-chip-hooks.js.map +1 -1
  3. package/dist/commands/codex-chip/prompt.js +1 -1
  4. package/dist/commands/codex-chip/prompt.js.map +1 -1
  5. package/dist/commands/collision-check.js +3 -2
  6. package/dist/commands/collision-check.js.map +1 -1
  7. package/dist/commands/fix.js +2 -2
  8. package/dist/commands/fix.js.map +1 -1
  9. package/dist/commands/friction-reminder.d.ts +1 -1
  10. package/dist/commands/friction-reminder.js +4 -4
  11. package/dist/commands/friction-reminder.js.map +1 -1
  12. package/dist/commands/init.js +2 -2
  13. package/dist/commands/mechanic-spawn.d.ts +2 -1
  14. package/dist/commands/mechanic-spawn.js +11 -9
  15. package/dist/commands/mechanic-spawn.js.map +1 -1
  16. package/dist/commands/mechanic.js +1 -1
  17. package/dist/commands/opencode-chip-mission.js +1 -1
  18. package/dist/commands/opencode-chip-mission.js.map +1 -1
  19. package/dist/commands/os-primer-reminder.js +1 -1
  20. package/dist/commands/os-primer-reminder.js.map +1 -1
  21. package/dist/hook.js +21 -5
  22. package/dist/hook.js.map +1 -1
  23. package/dist/native-skill-mirror.js +12 -0
  24. package/dist/native-skill-mirror.js.map +1 -1
  25. package/dist/opencode-plugin.bundle.js +629 -104
  26. package/dist/opencode-plugin.js +33 -0
  27. package/dist/opencode-plugin.js.map +1 -1
  28. package/dist/procedure-runtime.d.ts +10 -0
  29. package/dist/procedure-runtime.js +67 -0
  30. package/dist/procedure-runtime.js.map +1 -1
  31. package/dist/procedure.d.ts +4 -5
  32. package/dist/procedure.js +27 -34
  33. package/dist/procedure.js.map +1 -1
  34. package/package.json +1 -1
  35. package/skill/greprag/SKILL.md +1 -1
  36. package/skill/greprag/docs/codex-chip.md +5 -3
  37. package/skill/mechanic/SKILL.md +13 -11
  38. package/skill/templates/chip-spawn.md +2 -2
  39. package/skill/templates/codex-chip-spawn.md +7 -3
@@ -23,10 +23,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/opencode-plugin.ts
26
- var crypto2 = __toESM(require("crypto"));
27
- var fs4 = __toESM(require("fs"));
28
- var os = __toESM(require("os"));
29
- var path4 = __toESM(require("path"));
26
+ var crypto5 = __toESM(require("crypto"));
27
+ var fs7 = __toESM(require("fs"));
28
+ var os2 = __toESM(require("os"));
29
+ var path7 = __toESM(require("path"));
30
30
 
31
31
  // src/opencode-plugin-helpers.ts
32
32
  var crypto = __toESM(require("crypto"));
@@ -66,13 +66,13 @@ function readAnchorFile(filePath) {
66
66
  }
67
67
  }
68
68
  function findExistingAnchorFile(startDir) {
69
- const globalAnchorPath = path.join(HOME, ".greprag", "project.json");
70
- const legacyGlobalAnchorPath = path.join(HOME, ".claude", "project.json");
69
+ const globalAnchorPath2 = path.join(HOME, ".greprag", "project.json");
70
+ const legacyGlobalAnchorPath2 = path.join(HOME, ".claude", "project.json");
71
71
  let dir = path.resolve(startDir);
72
72
  while (true) {
73
73
  for (const subdir of [".greprag", ".claude", ".opencode"]) {
74
74
  const candidate = path.join(dir, subdir, "project.json");
75
- if (candidate !== globalAnchorPath && candidate !== legacyGlobalAnchorPath && fs.existsSync(candidate)) {
75
+ if (candidate !== globalAnchorPath2 && candidate !== legacyGlobalAnchorPath2 && fs.existsSync(candidate)) {
76
76
  return candidate;
77
77
  }
78
78
  }
@@ -1753,7 +1753,7 @@ function buildOsPrimer(env) {
1753
1753
  '\u2022 Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE \u2014 read these first" block naming exact paths.',
1754
1754
  "\u2022 Discoverability: every durable artifact must be findable next session \u2014 docs auto-register, skills auto-mirror, decisions get a dated ADR/decision-log entry, everything else gets its path named in the owning skill/doc. If nothing points at it, you didn't finish.",
1755
1755
  "\u2022 Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows. Named person/org/project/repo/customer/handle or unexplained proper noun \u2192 search Memory before guessing, unless fully defined in-turn.",
1756
- `\u2022 Friction \u21D2 fix NOW: type by durable repair surface (\`harness|doctrine|injection|env|code\`) \u21D2 \`greprag fix spawn --type <type> "<unit>"\`. It PRINTS a fix-chip mission \u2014 YOU then create the visible child task with that mission as its first message (how: \`greprag load ${spawnEntry}\`, or your harness's native task tool). Repo write = isolated worktree; data-only row/diagnosis = no repo write. One unit per chip; the chip fixes it, explains the friction/fix, then stops for approval. Never queue friction.`,
1756
+ `\u2022 Friction \u21D2 fix NOW: type by durable repair surface (\`harness|doctrine|injection|env|code\`) \u21D2 \`greprag fix spawn --type <type> "<unit>"\`. It PRINTS a fix-chip mission \u2014 YOU then create the visible child task with that mission as its first message (how: \`greprag load ${spawnEntry}\`, or your harness's native task tool). Repo write = isolated worktree; data-only row/diagnosis = no repo write. One unit per chip; the chip fixes, verifies, checkpoints, and hands it to the mission delivery owner. Never queue friction.`,
1757
1757
  "\u2022 Teach the system, not the chat: explained twice by the operator \u21D2 it belongs in a skill / load entry / STATE block / ADR, not the conversation."
1758
1758
  ].join("\n");
1759
1759
  }
@@ -2028,7 +2028,7 @@ function buildMechanicLiveNotice(_env) {
2028
2028
  function buildFrictionReminder(tier, env) {
2029
2029
  switch (tier) {
2030
2030
  case "nag":
2031
- return `\u26A0 FRICTION HIGH (live signal: repetition / errors / churn) \u2014 STOP pushing through. Name the ONE unit, choose type by repair surface, and spawn now: \`${FIX_SPAWN}\` (chip fixes it, explains the friction/fix, then stops for approval). No friction? say "clear" + continue.`;
2031
+ return `\u26A0 FRICTION HIGH (live signal: repetition / errors / churn) \u2014 STOP pushing through. Name the ONE unit, choose type by repair surface, and spawn now: \`${FIX_SPAWN}\` (chip fixes, verifies, checkpoints, and hands it to the mission delivery owner). No friction? say "clear" + continue.`;
2032
2032
  case "nudge":
2033
2033
  return `\u26A0 Friction detected (live signal) \u2014 repeated yourself, fought a tool, or hit a wall? Choose type by repair surface and spawn: \`${FIX_SPAWN}\`, then keep working. Otherwise say "clear" + continue.`;
2034
2034
  default:
@@ -2041,7 +2041,7 @@ function buildBootstrapFrictionReminder(env) {
2041
2041
  return null;
2042
2042
  const repeats = b.repeats && b.repeats > 1 ? ` repeated ${b.repeats}x` : "";
2043
2043
  const error = b.error ? ` Last error: ${b.error}` : "";
2044
- return `\u26A0 BOOTSTRAP FRICTION detected${repeats}: setup/toolchain failure (${b.signal || "setup"}) around \`${b.command || "unknown command"}\`.${error} If this is not caused by the code you just edited, spawn a fix chip now: \`greprag fix spawn --type env "${bootstrapSummary(env)}"\`. The chip owns the root-cause repair, explains it, then stops for approval so future sessions stop hitting it.`;
2044
+ return `\u26A0 BOOTSTRAP FRICTION detected${repeats}: setup/toolchain failure (${b.signal || "setup"}) around \`${b.command || "unknown command"}\`.${error} If this is not caused by the code you just edited, spawn a fix chip now: \`greprag fix spawn --type env "${bootstrapSummary(env)}"\`. The chip owns the root-cause repair, verifies it, and hands it to the mission delivery owner so future sessions stop hitting it.`;
2045
2045
  }
2046
2046
  function buildMechanicAnnounce() {
2047
2047
  return [
@@ -2049,7 +2049,7 @@ function buildMechanicAnnounce() {
2049
2049
  "WHEN friction happens \u2014 repeated yourself, fought a tool, got corrected >=2x on the same thing, rediscovered something already known, or hit a setup/toolchain failure not caused by code you just edited \u2014 spawn its fix chip NOW, then keep working. One unit = one chip; adjacent friction gets its own spawn. Never queue friction for later.",
2050
2050
  'TYPE = durable repair surface: harness=hooks/watchers/task dispatch; doctrine=greprag load/skills/AGENTS; injection=recap/Capture/doc-pointer/stateful injection; env=bootstrap/deps/scripts/worktree setup; code=product/source behavior. Use: `greprag fix spawn --type <type> "<one unit>"`.',
2051
2051
  "WORKSPACE ROUTING: `fix spawn` detects usable Git history before dispatch \u2014 Git uses an isolated worktree; non-Git, unavailable Git, or no commit uses the project-local task with serialized writes. No fail-then-fallback attempt.",
2052
- "THE CHIP'S CONTRACT (it self-enforces; you just spawn): identify the exact friction \u2192 make the smallest durable root-cause fix \u2192 explain the friction and fix in human terms \u2192 STOP for human approval before landing or cleanup.",
2052
+ "THE CHIP'S CONTRACT (it self-enforces; you just spawn): identify the exact friction \u2192 make the smallest durable root-cause fix \u2192 explain and verify it \u2192 checkpoint \u2192 hand it to the mission delivery owner. With no live parent and a full-goal mission, the chip becomes delivery owner and follows the repo profile. No second lifecycle approval.",
2053
2053
  "Every repair is ROOT-CAUSE \u2014 fix the pattern that makes the friction class possible, never a guard on today's trigger.",
2054
2054
  'A per-turn reminder fires ONLY when live friction is DETECTED (the stress signal \u2014 repetition / errors / churn), never on a timer \u2014 act on it, or say "clear" and continue.'
2055
2055
  ].join("\n");
@@ -2648,59 +2648,160 @@ function effectiveInstall(store, proc) {
2648
2648
  function defaultProcedureSettings(proc) {
2649
2649
  return Object.fromEntries(Object.entries(proc.settingsSchema ?? {}).map(([key, spec]) => [key, spec.default]));
2650
2650
  }
2651
- var REPO_BOUNDARY_PRECONDITION = [
2652
- "Before commit/merge/deploy/push/release: coordinate with reachable same-project peers and attribute every dirty file as intentional deploy work, peer-owned work, user-owned work, stale/discardable residue, or unknown.",
2653
- "Dirty checkout cleanup is deploy work: include yours/claimed intentional changes in the boundary artifact instead of escaping to a clean checkout or omitting them to make the checkout look clean.",
2654
- "Unknown ownership blocks the boundary until classified: exhaust local evidence and ask the human for direction if needed; overlapping peer work routes through handoff; stale generated residue is discarded only after evidence or human direction proves it is discardable.",
2655
- "Never stash, revert, sweep, or commit unrelated dirt just to appease a clean-tree deploy guard.",
2656
- "For tracked build output (for example site dist/), rebuild from the claimed source state and commit that generated artifact with its source; a separate deploy branch or worktree may isolate already-classified work, but a clean side checkout is not a substitute for settling pending repo work.",
2657
- "ABOUT TO COMMIT/MERGE/DEPLOY/PUSH/RELEASE after a skill guided the work? STOP - review the loaded skill(s): did this run reveal a durable rule, missing progressive-disclosure link, or method correction? If yes, run greprag load skill-change and update the canonical skill source only when the edit is narrow and low-risk; propose risky method changes instead of silently landing them. If no, say no skill update was needed. Never edit install artifacts as the source."
2658
- ].join(" ");
2651
+ function triggerHits(promptLower, triggerLower) {
2652
+ if (!triggerLower)
2653
+ return false;
2654
+ if (/\s/.test(triggerLower))
2655
+ return promptLower.includes(triggerLower);
2656
+ const re = new RegExp(`(^|[^a-z0-9])${escapeRegex(triggerLower)}([^a-z0-9]|$)`);
2657
+ return re.test(promptLower);
2658
+ }
2659
+ function escapeRegex(s) {
2660
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2661
+ }
2662
+ var INTENT_NEGATIONS = [
2663
+ "don't",
2664
+ "dont",
2665
+ "not",
2666
+ "no",
2667
+ "never",
2668
+ "without",
2669
+ "avoid",
2670
+ "won't",
2671
+ "wont",
2672
+ "didn't",
2673
+ "didnt",
2674
+ "cannot",
2675
+ "can't",
2676
+ "cant",
2677
+ "stop"
2678
+ ];
2679
+ var INTENT_COLLISION_PHRASES = [
2680
+ "push back",
2681
+ "push for",
2682
+ "push notification",
2683
+ "push notifications",
2684
+ "pushover",
2685
+ "merge conflict",
2686
+ "merge conflicts",
2687
+ "merge request",
2688
+ "merge sort",
2689
+ "press release",
2690
+ "release notes",
2691
+ "release candidate",
2692
+ "release date",
2693
+ "commit message",
2694
+ "commit hash",
2695
+ "commit history",
2696
+ "commit sha"
2697
+ ];
2698
+ function intentSuppressed(promptLower, triggerLower) {
2699
+ for (const phrase of INTENT_COLLISION_PHRASES) {
2700
+ if (phrase.includes(triggerLower) && promptLower.includes(phrase)) {
2701
+ const stripped = promptLower.split(phrase).join(" \xB7 ");
2702
+ if (!triggerHits(stripped, triggerLower))
2703
+ return true;
2704
+ }
2705
+ }
2706
+ const idx = promptLower.search(new RegExp(`(^|[^a-z0-9])${escapeRegex(triggerLower)}([^a-z0-9]|$)`));
2707
+ if (idx >= 0) {
2708
+ const before = promptLower.slice(0, idx).split(/[^a-z']+/).filter(Boolean);
2709
+ const window = " " + before.slice(-3).join(" ") + " ";
2710
+ if (INTENT_NEGATIONS.some((neg) => window.includes(" " + neg + " ")))
2711
+ return true;
2712
+ }
2713
+ return false;
2714
+ }
2715
+ function matchProcedure(prompt, store, opts = {}) {
2716
+ if (!prompt)
2717
+ return null;
2718
+ const promptLower = prompt.toLowerCase();
2719
+ let best = null;
2720
+ let bestLen = -1;
2721
+ for (const procedure of store.procedures) {
2722
+ if (!opts.includeInactive && effectiveInstall(store, procedure).state !== "active")
2723
+ continue;
2724
+ for (const trigger of procedure.triggers) {
2725
+ const t = trigger.toLowerCase();
2726
+ if (!triggerHits(promptLower, t))
2727
+ continue;
2728
+ if (!opts.ignoreIntentGuard && intentSuppressed(promptLower, t))
2729
+ continue;
2730
+ if (t.length > bestLen) {
2731
+ best = { procedure, trigger };
2732
+ bestLen = t.length;
2733
+ }
2734
+ }
2735
+ }
2736
+ return best;
2737
+ }
2738
+ function buildProcedureInjection(proc) {
2739
+ const lines = [];
2740
+ const tag = proc.destructive ? " \u26A0 DESTRUCTIVE" : "";
2741
+ lines.push(`[Procedure: ${proc.verb} \u2014 ${proc.status}${tag}] This repo has a known recipe for "${proc.verb}".`);
2742
+ if (proc.preconditions)
2743
+ lines.push(` PRECONDITIONS: ${proc.preconditions}`);
2744
+ if (proc.destructive && proc.caveats) {
2745
+ lines.push(` \u26D4 GATE: ${proc.caveats}`);
2746
+ lines.push(` STEPS (only after approval): ${proc.steps}`);
2747
+ } else {
2748
+ lines.push(` STEPS: ${proc.steps}`);
2749
+ if (proc.caveats)
2750
+ lines.push(` GATE: ${proc.caveats}`);
2751
+ }
2752
+ if (proc.endpoint)
2753
+ lines.push(` ENDPOINT (done when): ${proc.endpoint}`);
2754
+ lines.push(
2755
+ proc.destructive ? ` Follow the procedure's gate for effects outside ordinary repo delivery. For commit/merge/push/deploy/release, the current full-goal mission and Delivery Profile are the authority; Procedure adds no second approval.` : ` Use this recipe. If it no longer works, say so \u2014 that's a stale procedure to re-learn.`
2756
+ );
2757
+ return lines.join("\n");
2758
+ }
2759
+ var LIFECYCLE_SHADOW_PRECONDITION = "Shadow evidence only. The Delivery Profile and canonical lifecycle skills own active repo-boundary behavior.";
2659
2760
  var GIT_SEED_SET = [
2660
2761
  {
2661
2762
  verb: "commit",
2662
2763
  triggers: ["commit", "check in", "check it in", "checkpoint this", "land it"],
2663
- preconditions: REPO_BOUNDARY_PRECONDITION,
2664
- steps: 'git add -A && git commit -m "wip: <msg>"; then merge the feature branch into the default branch and run the repo-native cleanup/archive flow.',
2665
- caveats: "Scope = task-owned work only. Do not sweep a parallel workstream's pending changes. STOPS before push.",
2666
- endpoint: "changes committed and the feature branch merged into default",
2764
+ preconditions: LIFECYCLE_SHADOW_PRECONDITION,
2765
+ steps: "Shadow capture only. Active behavior resolves the repo Delivery Profile and canonical commit skill.",
2766
+ caveats: "No lifecycle guidance is injected from Procedure while the announce-first pilot is active.",
2767
+ endpoint: "observed checkpoint/integration span is no longer live",
2667
2768
  status: "seeded"
2668
2769
  },
2669
2770
  {
2670
2771
  verb: "merge",
2671
2772
  triggers: ["merge", "merge the branch", "merge to main", "merge into main"],
2672
- preconditions: REPO_BOUNDARY_PRECONDITION,
2673
- steps: "git checkout <default> && git merge <branch> (folded into the commit flow).",
2674
- caveats: "Do not treat a clean current checkout as whole-repo clearance.",
2675
- endpoint: "branch merged into the default branch with no conflicts",
2773
+ preconditions: LIFECYCLE_SHADOW_PRECONDITION,
2774
+ steps: "Shadow capture only. Active behavior resolves the profile integration strategy and coordinates peers through the harness adapter.",
2775
+ caveats: "Procedure records evidence; it does not choose a branch or merge strategy.",
2776
+ endpoint: "observed integration span is no longer live",
2676
2777
  status: "seeded"
2677
2778
  },
2678
2779
  {
2679
2780
  verb: "push",
2680
2781
  triggers: ["push", "git push", "push to remote", "push it up", "push upstream"],
2681
- preconditions: REPO_BOUNDARY_PRECONDITION,
2682
- steps: "git push",
2683
- caveats: "git push triggers a PRODUCTION deploy. STOP and get explicit fresh approval before pushing unless the current mission explicitly provisions release/deploy completion.",
2684
- endpoint: "operator approved the push AND it completed",
2782
+ preconditions: LIFECYCLE_SHADOW_PRECONDITION,
2783
+ steps: "Shadow capture only. Active behavior follows the repo profile's remote, default branch, and push/deploy coupling.",
2784
+ caveats: "Seed-only destructive vocabulary; never learn or inject a push command.",
2785
+ endpoint: "observed push span is no longer live",
2685
2786
  status: "seeded",
2686
2787
  destructive: true
2687
2788
  },
2688
2789
  {
2689
2790
  verb: "deploy",
2690
2791
  triggers: ["deploy", "deploy the api", "deploy the worker", "redeploy"],
2691
- preconditions: REPO_BOUNDARY_PRECONDITION,
2692
- steps: "Resolve the repo/target DeploySchema for this deploy intent; run only that schema's commands after preconditions are settled.",
2693
- caveats: "Method-agnostic: the global seed only routes to a learned repo/target DeploySchema. Do not infer provider/tooling from another repo. If the schema is missing, stale, or target-ambiguous, say so and capture the live resolution instead of guessing.",
2694
- endpoint: "the target DeploySchema success signal and verification pass; missing/stale schema is captured without registering a recipe",
2792
+ preconditions: LIFECYCLE_SHADOW_PRECONDITION,
2793
+ steps: "Shadow capture only. Active behavior resolves `.greprag/delivery.json` and its declared deploy document.",
2794
+ caveats: "Procedure does not infer, learn, or inject provider commands during the pilot.",
2795
+ endpoint: "observed deploy/verification span is no longer live",
2695
2796
  status: "seeded"
2696
2797
  },
2697
2798
  {
2698
2799
  verb: "release",
2699
2800
  triggers: ["release", "ship it", "ship a release", "cut a release", "publish the release", "do a release"],
2700
- preconditions: REPO_BOUNDARY_PRECONDITION,
2701
- steps: "Run the /release flow \u2014 greprag's actual ship path (version bump + publish).",
2702
- caveats: "greprag ships via /release \u2014 NOT /commit and NOT /deploy. Do not conflate the three: commit lands code, deploy pushes the worker, release publishes a version.",
2703
- endpoint: "the release is published (new version live)",
2801
+ preconditions: LIFECYCLE_SHADOW_PRECONDITION,
2802
+ steps: "Shadow capture only. Active behavior follows the repo profile's release document and canonical release skill.",
2803
+ caveats: "Procedure does not infer package, tag, registry, provider, or version policy.",
2804
+ endpoint: "observed release/verification span is no longer live",
2704
2805
  status: "seeded"
2705
2806
  }
2706
2807
  ];
@@ -2728,6 +2829,404 @@ function activeProcedureAnnounces(store) {
2728
2829
  return lines;
2729
2830
  }
2730
2831
 
2832
+ // src/procedure-runtime.ts
2833
+ var crypto4 = __toESM(require("crypto"));
2834
+
2835
+ // src/project-anchor.ts
2836
+ var path3 = __toESM(require("path"));
2837
+ var fs3 = __toESM(require("fs"));
2838
+ var crypto2 = __toESM(require("crypto"));
2839
+ var os = __toESM(require("os"));
2840
+ var ANCHOR_DIR = ".greprag";
2841
+ var ANCHOR_FILE = "project.json";
2842
+ var LEGACY_ANCHOR_DIR = ".claude";
2843
+ function anchorPathIn(dir) {
2844
+ return path3.join(dir, ANCHOR_DIR, ANCHOR_FILE);
2845
+ }
2846
+ function legacyAnchorPathIn(dir) {
2847
+ return path3.join(dir, LEGACY_ANCHOR_DIR, ANCHOR_FILE);
2848
+ }
2849
+ function globalAnchorPath() {
2850
+ return anchorPathIn(os.homedir());
2851
+ }
2852
+ function legacyGlobalAnchorPath() {
2853
+ return legacyAnchorPathIn(os.homedir());
2854
+ }
2855
+ function findExistingAnchor(startDir) {
2856
+ const homeAnchor = globalAnchorPath();
2857
+ const legacyHomeAnchor = legacyGlobalAnchorPath();
2858
+ let dir = path3.resolve(startDir);
2859
+ while (true) {
2860
+ const candidate = anchorPathIn(dir);
2861
+ if (candidate !== homeAnchor && fs3.existsSync(candidate))
2862
+ return candidate;
2863
+ const legacyCandidate = legacyAnchorPathIn(dir);
2864
+ if (legacyCandidate !== legacyHomeAnchor && fs3.existsSync(legacyCandidate))
2865
+ return legacyCandidate;
2866
+ const parent = path3.dirname(dir);
2867
+ if (parent === dir)
2868
+ return null;
2869
+ dir = parent;
2870
+ }
2871
+ }
2872
+ function isEphemeralCwd2(cwd) {
2873
+ const norm = path3.resolve(cwd).replace(/\\/g, "/").toLowerCase();
2874
+ if (norm.includes("/appdata/roaming/claude/local-agent-mode-sessions/"))
2875
+ return true;
2876
+ if (norm.includes("/appdata/local/claude/local-agent-mode-sessions/"))
2877
+ return true;
2878
+ if (norm.startsWith("/tmp/"))
2879
+ return true;
2880
+ if (norm.startsWith("/var/tmp/"))
2881
+ return true;
2882
+ if (norm.startsWith("/private/tmp/"))
2883
+ return true;
2884
+ return false;
2885
+ }
2886
+ function computeGitDerivedProjectId2(workingDir) {
2887
+ try {
2888
+ const out = safeExecSync("git rev-list --max-parents=0 HEAD", {
2889
+ cwd: workingDir,
2890
+ encoding: "utf-8",
2891
+ stdio: ["pipe", "pipe", "pipe"]
2892
+ });
2893
+ const roots = out.trim().split(/\s+/).filter(Boolean).sort();
2894
+ if (roots.length === 0)
2895
+ return null;
2896
+ const hash = crypto2.createHash("sha256").update(roots.join("\n")).digest("hex");
2897
+ return [
2898
+ hash.slice(0, 8),
2899
+ hash.slice(8, 12),
2900
+ "4" + hash.slice(13, 16),
2901
+ // version 4 nibble
2902
+ "8" + hash.slice(17, 20),
2903
+ // variant nibble
2904
+ hash.slice(20, 32)
2905
+ ].join("-");
2906
+ } catch {
2907
+ return null;
2908
+ }
2909
+ }
2910
+ function deterministicProjectId2(workingDir) {
2911
+ const normalized = path3.resolve(workingDir).toLowerCase();
2912
+ const hash = crypto2.createHash("sha256").update(normalized).digest("hex");
2913
+ return [
2914
+ hash.slice(0, 8),
2915
+ hash.slice(8, 12),
2916
+ "4" + hash.slice(13, 16),
2917
+ // version 4 nibble
2918
+ "8" + hash.slice(17, 20),
2919
+ // variant nibble
2920
+ hash.slice(20, 32)
2921
+ ].join("-");
2922
+ }
2923
+ function tryReadAnchorFileContents(filePath) {
2924
+ try {
2925
+ const raw = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2926
+ const notifyRaw = raw.inbox_notify;
2927
+ const inboxNotify = notifyRaw === "off" || notifyRaw === "session_start_only" ? notifyRaw : "every_turn";
2928
+ return {
2929
+ projectId: raw.project_id,
2930
+ projectName: raw.project_name,
2931
+ created: raw.created,
2932
+ memoryCapture: raw.memory_capture !== false,
2933
+ sessionStartRecap: raw.session_start_recap !== false,
2934
+ inboxNotify,
2935
+ emailDir: typeof raw.email_dir === "string" ? raw.email_dir : void 0,
2936
+ emailAutosave: raw.email_autosave === true,
2937
+ role: typeof raw.role === "string" ? raw.role : void 0
2938
+ };
2939
+ } catch {
2940
+ return null;
2941
+ }
2942
+ }
2943
+ function readAnchor2(cwd) {
2944
+ const existingPath = findExistingAnchor(cwd);
2945
+ const fileContents = existingPath ? tryReadAnchorFileContents(existingPath) : null;
2946
+ if (existingPath && fileContents && fileContents.projectId && fileContents.projectName) {
2947
+ return {
2948
+ projectId: fileContents.projectId,
2949
+ projectName: fileContents.projectName,
2950
+ initialized: true,
2951
+ source: "file",
2952
+ anchorPath: existingPath,
2953
+ created: fileContents.created,
2954
+ memoryCapture: fileContents.memoryCapture,
2955
+ sessionStartRecap: fileContents.sessionStartRecap,
2956
+ inboxNotify: fileContents.inboxNotify,
2957
+ emailDir: fileContents.emailDir,
2958
+ emailAutosave: fileContents.emailAutosave,
2959
+ role: fileContents.role
2960
+ };
2961
+ }
2962
+ const gitId = computeGitDerivedProjectId2(cwd);
2963
+ if (gitId) {
2964
+ const root2 = path3.resolve(cwd);
2965
+ return {
2966
+ projectId: gitId,
2967
+ projectName: fileContents?.projectName || path3.basename(root2).toLowerCase(),
2968
+ initialized: true,
2969
+ source: "git",
2970
+ anchorPath: existingPath || anchorPathIn(root2),
2971
+ created: fileContents?.created,
2972
+ memoryCapture: fileContents?.memoryCapture ?? true,
2973
+ sessionStartRecap: fileContents?.sessionStartRecap ?? true,
2974
+ inboxNotify: fileContents?.inboxNotify ?? "every_turn",
2975
+ emailDir: fileContents?.emailDir,
2976
+ emailAutosave: fileContents?.emailAutosave,
2977
+ role: fileContents?.role
2978
+ };
2979
+ }
2980
+ if (isEphemeralCwd2(cwd)) {
2981
+ const globalPath = fs3.existsSync(globalAnchorPath()) ? globalAnchorPath() : legacyGlobalAnchorPath();
2982
+ const globalContents = tryReadAnchorFileContents(globalPath);
2983
+ if (globalContents && globalContents.projectId && globalContents.projectName) {
2984
+ return {
2985
+ projectId: globalContents.projectId,
2986
+ projectName: globalContents.projectName,
2987
+ initialized: true,
2988
+ source: "global",
2989
+ anchorPath: globalPath,
2990
+ created: globalContents.created,
2991
+ memoryCapture: globalContents.memoryCapture,
2992
+ sessionStartRecap: globalContents.sessionStartRecap,
2993
+ inboxNotify: globalContents.inboxNotify,
2994
+ emailDir: globalContents.emailDir,
2995
+ emailAutosave: globalContents.emailAutosave,
2996
+ role: globalContents.role
2997
+ };
2998
+ }
2999
+ }
3000
+ const root = path3.resolve(cwd);
3001
+ return {
3002
+ projectId: deterministicProjectId2(root),
3003
+ projectName: fileContents?.projectName || path3.basename(root).toLowerCase(),
3004
+ initialized: false,
3005
+ source: "hash",
3006
+ anchorPath: existingPath || anchorPathIn(root),
3007
+ created: fileContents?.created,
3008
+ memoryCapture: fileContents?.memoryCapture ?? true,
3009
+ sessionStartRecap: fileContents?.sessionStartRecap ?? true,
3010
+ inboxNotify: fileContents?.inboxNotify ?? "every_turn",
3011
+ emailDir: fileContents?.emailDir,
3012
+ emailAutosave: fileContents?.emailAutosave,
3013
+ role: fileContents?.role
3014
+ };
3015
+ }
3016
+
3017
+ // src/procedure-watch.ts
3018
+ var path4 = __toESM(require("path"));
3019
+ var fs4 = __toESM(require("fs"));
3020
+ var TIER1_LEARN_TRIGGERS = [
3021
+ { verb: "deploy", triggers: ["deploy", "deploy the api", "deploy the worker", "redeploy"], steps: "", status: "seeded" },
3022
+ { verb: "push", triggers: ["push", "git push", "push to remote", "push it up", "push upstream"], steps: "", status: "seeded", destructive: true },
3023
+ { verb: "release", triggers: ["release", "ship", "ship it", "ship a release", "cut a release", "publish the release", "do a release"], steps: "", status: "seeded" },
3024
+ { verb: "build", triggers: ["build", "rebuild", "compile", "run the build"], steps: "", status: "seeded" },
3025
+ { verb: "test", triggers: ["test", "tests", "run the tests", "run tests"], steps: "", status: "seeded" }
3026
+ ];
3027
+ function matchLearnTrigger(prompt) {
3028
+ return matchProcedure(prompt, {
3029
+ version: PROCEDURE_STORE_VERSION,
3030
+ procedures: TIER1_LEARN_TRIGGERS
3031
+ });
3032
+ }
3033
+ function isDestructiveVerb(verb, store) {
3034
+ if (store.procedures.some((p) => p.verb === verb && p.destructive))
3035
+ return true;
3036
+ return TIER1_LEARN_TRIGGERS.some((p) => p.verb === verb && p.destructive);
3037
+ }
3038
+ function openWatch(verb, phase, openedAt, shadowRunId) {
3039
+ return {
3040
+ verb,
3041
+ phase,
3042
+ shadowRunId,
3043
+ openedAt: openedAt || (/* @__PURE__ */ new Date()).toISOString(),
3044
+ turnCount: 0,
3045
+ observations: [],
3046
+ lastLiveTurn: 0,
3047
+ offStreak: 0
3048
+ };
3049
+ }
3050
+ function stateDir2() {
3051
+ const home = process.env.HOME || process.env.USERPROFILE || "";
3052
+ return path4.join(home, ".greprag", "state");
3053
+ }
3054
+ function procedureWatchPath(projectId) {
3055
+ return path4.join(stateDir2(), `procedure-watch-${projectId}.json`);
3056
+ }
3057
+ function hasProcedureWatch(projectId) {
3058
+ try {
3059
+ return fs4.existsSync(procedureWatchPath(projectId));
3060
+ } catch {
3061
+ return false;
3062
+ }
3063
+ }
3064
+ function writeProcedureWatch(projectId, watch) {
3065
+ const file = procedureWatchPath(projectId);
3066
+ const dir = path4.dirname(file);
3067
+ if (!fs4.existsSync(dir))
3068
+ fs4.mkdirSync(dir, { recursive: true });
3069
+ fs4.writeFileSync(file, JSON.stringify(watch, null, 2) + "\n");
3070
+ }
3071
+ function openWatchIfIdle(projectId, verb, phase, shadowRunId) {
3072
+ if (hasProcedureWatch(projectId))
3073
+ return false;
3074
+ writeProcedureWatch(projectId, openWatch(verb, phase, void 0, shadowRunId));
3075
+ return true;
3076
+ }
3077
+
3078
+ // src/procedure-shadow.ts
3079
+ var crypto3 = __toESM(require("crypto"));
3080
+ var fs5 = __toESM(require("fs"));
3081
+ var path5 = __toESM(require("path"));
3082
+ function stateDir3() {
3083
+ const home = process.env.HOME || process.env.USERPROFILE || "";
3084
+ return path5.join(home, ".greprag", "state");
3085
+ }
3086
+ function procedureShadowPath(projectId) {
3087
+ return path5.join(stateDir3(), `procedure-shadow-${projectId}.jsonl`);
3088
+ }
3089
+ function appendShadowEvent(projectId, event) {
3090
+ try {
3091
+ const file = procedureShadowPath(projectId);
3092
+ fs5.mkdirSync(path5.dirname(file), { recursive: true });
3093
+ fs5.appendFileSync(file, JSON.stringify(event) + "\n");
3094
+ } catch {
3095
+ }
3096
+ }
3097
+ function createProcedureShadowRunId() {
3098
+ return crypto3.randomUUID();
3099
+ }
3100
+ function procedureFingerprint(proc) {
3101
+ const payload = JSON.stringify({
3102
+ verb: proc.verb,
3103
+ triggers: proc.triggers,
3104
+ preconditions: proc.preconditions,
3105
+ steps: proc.steps,
3106
+ caveats: proc.caveats,
3107
+ endpoint: proc.endpoint,
3108
+ definitionVersion: proc.definitionVersion ?? 1
3109
+ });
3110
+ return crypto3.createHash("sha256").update(payload).digest("hex");
3111
+ }
3112
+ function recordProcedureShadowTrigger(params) {
3113
+ appendShadowEvent(params.projectId, {
3114
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3115
+ kind: "would-have-fired",
3116
+ runId: params.runId,
3117
+ projectId: params.projectId,
3118
+ verb: params.match.procedure.verb,
3119
+ trigger: params.match.trigger,
3120
+ procedureStatus: params.match.procedure.status,
3121
+ definitionVersion: params.match.procedure.definitionVersion ?? 1,
3122
+ configuredState: params.configuredState,
3123
+ runtimeState: "shadow",
3124
+ schemaFingerprint: procedureFingerprint(params.match.procedure),
3125
+ watchOpened: params.watchOpened,
3126
+ sessionId: params.sessionId || void 0
3127
+ });
3128
+ }
3129
+ function recordMissingLifecycleShadowTrigger(params) {
3130
+ appendShadowEvent(params.projectId, {
3131
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3132
+ kind: "would-have-fired",
3133
+ runId: params.runId,
3134
+ projectId: params.projectId,
3135
+ verb: params.verb,
3136
+ trigger: params.trigger,
3137
+ configuredState: "missing",
3138
+ runtimeState: "shadow",
3139
+ watchOpened: false,
3140
+ sessionId: params.sessionId || void 0
3141
+ });
3142
+ }
3143
+
3144
+ // src/procedure-runtime.ts
3145
+ function extractLatestOpenCodeProcedurePrompt(messages) {
3146
+ let userCount = 0;
3147
+ let latest = null;
3148
+ for (const raw of messages) {
3149
+ if (!raw || typeof raw !== "object")
3150
+ continue;
3151
+ const msg = raw;
3152
+ if (msg.info?.role !== "user" || !Array.isArray(msg.parts))
3153
+ continue;
3154
+ userCount++;
3155
+ const parts = msg.parts;
3156
+ const prompt = parts.filter((part) => !!part && typeof part === "object").filter((part) => part.type === "text" && typeof part.text === "string" && part.synthetic !== true && part.ignored !== true).map((part) => String(part.text)).join("\n").trim();
3157
+ if (prompt)
3158
+ latest = { prompt, parts };
3159
+ }
3160
+ if (!latest)
3161
+ return null;
3162
+ const digest = crypto4.createHash("sha256").update(latest.prompt).digest("hex");
3163
+ return { ...latest, key: `${userCount}:${digest}` };
3164
+ }
3165
+ function procedureRuntimeState(configuredState, proc) {
3166
+ if (configuredState === "disabled")
3167
+ return "disabled";
3168
+ return isDeliveryLifecycleVerb(proc.verb) ? "shadow" : configuredState;
3169
+ }
3170
+ function runProcedurePromptCheck(params) {
3171
+ try {
3172
+ if (!params.prompt.trim())
3173
+ return { mode: "none" };
3174
+ const anchor = readAnchor2(params.cwd);
3175
+ if (!anchor.projectId)
3176
+ return { mode: "none" };
3177
+ const store = readProcedureStore(anchor.projectId);
3178
+ const raw = store.procedures.length ? matchProcedure(params.prompt, store, { includeInactive: true }) : null;
3179
+ if (raw) {
3180
+ const configuredState = effectiveInstall(store, raw.procedure).state;
3181
+ const runtimeState = procedureRuntimeState(configuredState, raw.procedure);
3182
+ if (runtimeState === "disabled") {
3183
+ return { mode: "disabled", verb: raw.procedure.verb };
3184
+ }
3185
+ if (runtimeState === "shadow") {
3186
+ const runId = createProcedureShadowRunId();
3187
+ const watchOpened = raw.procedure.status !== "stale" ? openWatchIfIdle(anchor.projectId, raw.procedure.verb, "SHADOW", runId) : false;
3188
+ recordProcedureShadowTrigger({
3189
+ projectId: anchor.projectId,
3190
+ runId,
3191
+ match: raw,
3192
+ configuredState,
3193
+ watchOpened,
3194
+ sessionId: params.sessionId
3195
+ });
3196
+ return { mode: "shadow", verb: raw.procedure.verb };
3197
+ }
3198
+ if (raw.procedure.status !== "stale") {
3199
+ openWatchIfIdle(anchor.projectId, raw.procedure.verb, "REPLAY");
3200
+ return {
3201
+ mode: "active",
3202
+ verb: raw.procedure.verb,
3203
+ additionalContext: buildProcedureInjection(raw.procedure)
3204
+ };
3205
+ }
3206
+ }
3207
+ const learned = raw || matchLearnTrigger(params.prompt);
3208
+ if (!learned)
3209
+ return { mode: "none" };
3210
+ if (isDeliveryLifecycleVerb(learned.procedure.verb)) {
3211
+ recordMissingLifecycleShadowTrigger({
3212
+ projectId: anchor.projectId,
3213
+ runId: createProcedureShadowRunId(),
3214
+ verb: learned.procedure.verb,
3215
+ trigger: learned.trigger,
3216
+ sessionId: params.sessionId
3217
+ });
3218
+ return { mode: "shadow", verb: learned.procedure.verb };
3219
+ }
3220
+ if (isDestructiveVerb(learned.procedure.verb, store)) {
3221
+ return { mode: "disabled", verb: learned.procedure.verb };
3222
+ }
3223
+ openWatchIfIdle(anchor.projectId, learned.procedure.verb, "LEARN");
3224
+ return { mode: "learn", verb: learned.procedure.verb };
3225
+ } catch {
3226
+ return { mode: "none" };
3227
+ }
3228
+ }
3229
+
2731
3230
  // src/archive/glyph-font.ts
2732
3231
  var GLYPH_W = 8;
2733
3232
  var GLYPH_H = 8;
@@ -3311,29 +3810,29 @@ function recodeMessagesToPng(messages, opts) {
3311
3810
  }
3312
3811
 
3313
3812
  // src/skill-activation-manifest.ts
3314
- var fs3 = __toESM(require("fs"));
3315
- var path3 = __toESM(require("path"));
3813
+ var fs6 = __toESM(require("fs"));
3814
+ var path6 = __toESM(require("path"));
3316
3815
  var MAX_NATIVE_SKILL_FILES = 1500;
3317
3816
  var MAX_SCAN_DEPTH = 7;
3318
3817
  function homeRoots(homeDir, platform) {
3319
3818
  if (platform === "claude-code") {
3320
- return [path3.join(homeDir, ".claude", "skills")];
3819
+ return [path6.join(homeDir, ".claude", "skills")];
3321
3820
  }
3322
3821
  if (platform === "codex") {
3323
3822
  return [
3324
- path3.join(homeDir, ".codex", "skills"),
3325
- path3.join(homeDir, ".agents", "skills"),
3326
- path3.join(homeDir, ".codex", "plugins", "cache")
3823
+ path6.join(homeDir, ".codex", "skills"),
3824
+ path6.join(homeDir, ".agents", "skills"),
3825
+ path6.join(homeDir, ".codex", "plugins", "cache")
3327
3826
  ];
3328
3827
  }
3329
- return [path3.join(homeDir, ".config", "opencode", "skills")];
3828
+ return [path6.join(homeDir, ".config", "opencode", "skills")];
3330
3829
  }
3331
3830
  function ancestorDirs(cwd) {
3332
3831
  const dirs = [];
3333
- let current = path3.resolve(cwd);
3832
+ let current = path6.resolve(cwd);
3334
3833
  for (let depth = 0; depth < 16; depth++) {
3335
3834
  dirs.push(current);
3336
- const parent = path3.dirname(current);
3835
+ const parent = path6.dirname(current);
3337
3836
  if (parent === current)
3338
3837
  break;
3339
3838
  current = parent;
@@ -3343,11 +3842,11 @@ function ancestorDirs(cwd) {
3343
3842
  function repoRoots(cwd, platform) {
3344
3843
  return ancestorDirs(cwd).flatMap((dir) => {
3345
3844
  if (platform === "claude-code")
3346
- return [path3.join(dir, ".claude", "skills")];
3845
+ return [path6.join(dir, ".claude", "skills")];
3347
3846
  if (platform === "codex") {
3348
- return [path3.join(dir, ".codex", "skills"), path3.join(dir, ".agents", "skills")];
3847
+ return [path6.join(dir, ".codex", "skills"), path6.join(dir, ".agents", "skills")];
3349
3848
  }
3350
- return [path3.join(dir, ".opencode", "skills")];
3849
+ return [path6.join(dir, ".opencode", "skills")];
3351
3850
  });
3352
3851
  }
3353
3852
  function frontmatterName(content) {
@@ -3363,14 +3862,14 @@ function readNativeSkillNames(params) {
3363
3862
  return;
3364
3863
  let entries;
3365
3864
  try {
3366
- entries = fs3.readdirSync(dir, { withFileTypes: true });
3865
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
3367
3866
  } catch {
3368
3867
  return;
3369
3868
  }
3370
3869
  for (const entry of entries) {
3371
3870
  if (visited >= MAX_NATIVE_SKILL_FILES)
3372
3871
  return;
3373
- const full = path3.join(dir, entry.name);
3872
+ const full = path6.join(dir, entry.name);
3374
3873
  if (entry.isDirectory()) {
3375
3874
  walk(full, depth + 1);
3376
3875
  continue;
@@ -3378,9 +3877,9 @@ function readNativeSkillNames(params) {
3378
3877
  if (!entry.isFile() || entry.name.toLowerCase() !== "skill.md")
3379
3878
  continue;
3380
3879
  visited++;
3381
- names.add(path3.basename(path3.dirname(full)).toLowerCase());
3880
+ names.add(path6.basename(path6.dirname(full)).toLowerCase());
3382
3881
  try {
3383
- const declared = frontmatterName(fs3.readFileSync(full, "utf8"));
3882
+ const declared = frontmatterName(fs6.readFileSync(full, "utf8"));
3384
3883
  if (declared)
3385
3884
  names.add(declared.toLowerCase());
3386
3885
  } catch {
@@ -3412,14 +3911,14 @@ function activationFromApiRows(params) {
3412
3911
  }
3413
3912
 
3414
3913
  // src/opencode-plugin.ts
3415
- var DEBUG_LOG_PATH = path4.join(os.homedir(), ".greprag", "opencode-plugin-debug.log");
3914
+ var DEBUG_LOG_PATH = path7.join(os2.homedir(), ".greprag", "opencode-plugin-debug.log");
3416
3915
  var _debugLogReady = false;
3417
3916
  function dlogInit() {
3418
3917
  if (_debugLogReady)
3419
3918
  return;
3420
3919
  _debugLogReady = true;
3421
3920
  try {
3422
- fs4.writeFileSync(DEBUG_LOG_PATH, "");
3921
+ fs7.writeFileSync(DEBUG_LOG_PATH, "");
3423
3922
  } catch {
3424
3923
  }
3425
3924
  }
@@ -3428,7 +3927,7 @@ function dlog(msg) {
3428
3927
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [greprag-memory] ${msg}
3429
3928
  `;
3430
3929
  try {
3431
- fs4.appendFileSync(DEBUG_LOG_PATH, line);
3930
+ fs7.appendFileSync(DEBUG_LOG_PATH, line);
3432
3931
  } catch {
3433
3932
  }
3434
3933
  }
@@ -3436,9 +3935,9 @@ dlog(`module top reached: pid=${process.pid} argv0=${process.argv[0]} distPath=$
3436
3935
  var API_URL = "https://api.greprag.com";
3437
3936
  function loadEnvFile(filePath) {
3438
3937
  try {
3439
- if (!fs4.existsSync(filePath))
3938
+ if (!fs7.existsSync(filePath))
3440
3939
  return;
3441
- const raw = fs4.readFileSync(filePath, "utf-8");
3940
+ const raw = fs7.readFileSync(filePath, "utf-8");
3442
3941
  for (const line of raw.split(/\r?\n/)) {
3443
3942
  const trimmed = line.trim();
3444
3943
  if (!trimmed || trimmed.startsWith("#"))
@@ -3458,12 +3957,12 @@ function loadEnvFile(filePath) {
3458
3957
  }
3459
3958
  }
3460
3959
  function loadGrepragEnv() {
3461
- loadEnvFile(path4.join(HOME, ".greprag", ".env"));
3960
+ loadEnvFile(path7.join(HOME, ".greprag", ".env"));
3462
3961
  try {
3463
- const p = path4.join(HOME, ".claude", "settings.json");
3464
- if (!fs4.existsSync(p))
3962
+ const p = path7.join(HOME, ".claude", "settings.json");
3963
+ if (!fs7.existsSync(p))
3465
3964
  return;
3466
- const data = JSON.parse(fs4.readFileSync(p, "utf-8"));
3965
+ const data = JSON.parse(fs7.readFileSync(p, "utf-8"));
3467
3966
  if (data && data.env && typeof data.env === "object") {
3468
3967
  for (const [key, val] of Object.entries(data.env)) {
3469
3968
  if (!process.env[key] && typeof val === "string") {
@@ -3476,11 +3975,11 @@ function loadGrepragEnv() {
3476
3975
  }
3477
3976
  loadGrepragEnv();
3478
3977
  dlog(`env loaded: MEMORY_HOOK_ENABLED=${process.env.MEMORY_HOOK_ENABLED || "<unset>"} GREPRAG_API_KEY=${process.env.GREPRAG_API_KEY ? "set" : "<unset>"} GREPRAG_OPENCODE_CAPTURE=${process.env.GREPRAG_OPENCODE_CAPTURE || "<unset>"}`);
3479
- var WATCHER_LOCK = path4.join(HOME || os.homedir(), ".greprag", "opencode-watch.lock");
3978
+ var WATCHER_LOCK = path7.join(HOME || os2.homedir(), ".greprag", "opencode-watch.lock");
3480
3979
  var WATCHER_STALE_MS = 3e4;
3481
3980
  function findGrepragBinary() {
3482
3981
  const override = process.env.GREPRAG_BIN;
3483
- if (override && fs4.existsSync(override))
3982
+ if (override && fs7.existsSync(override))
3484
3983
  return override;
3485
3984
  const binaryName = process.platform === "win32" ? "greprag.cmd" : "greprag";
3486
3985
  try {
@@ -3489,14 +3988,14 @@ function findGrepragBinary() {
3489
3988
  const candidate = line.trim();
3490
3989
  if (!candidate)
3491
3990
  continue;
3492
- if (!fs4.existsSync(candidate))
3991
+ if (!fs7.existsSync(candidate))
3493
3992
  continue;
3494
3993
  if (process.platform === "win32") {
3495
- const ext = path4.extname(candidate).toLowerCase();
3994
+ const ext = path7.extname(candidate).toLowerCase();
3496
3995
  if (ext === ".cmd" || ext === ".exe" || ext === ".bat")
3497
3996
  return candidate;
3498
3997
  const cmdSibling = candidate + ".cmd";
3499
- if (fs4.existsSync(cmdSibling))
3998
+ if (fs7.existsSync(cmdSibling))
3500
3999
  return cmdSibling;
3501
4000
  continue;
3502
4001
  }
@@ -3505,19 +4004,19 @@ function findGrepragBinary() {
3505
4004
  } catch {
3506
4005
  }
3507
4006
  const candidates = process.platform === "win32" ? [
3508
- path4.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
3509
- path4.join(HOME, "AppData", "Roaming", "npm", "greprag"),
3510
- path4.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
3511
- path4.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
4007
+ path7.join(HOME, "AppData", "Roaming", "npm", "greprag.cmd"),
4008
+ path7.join(HOME, "AppData", "Roaming", "npm", "greprag"),
4009
+ path7.join(HOME, "AppData", "Local", "Yarn", "bin", "greprag.cmd"),
4010
+ path7.join(HOME, "AppData", "Local", "pnpm", "bin", "greprag.cmd")
3512
4011
  ] : [
3513
4012
  "/usr/local/bin/greprag",
3514
4013
  "/opt/homebrew/bin/greprag",
3515
- path4.join(HOME || "", ".local", "bin", "greprag"),
3516
- path4.join(HOME || "", ".yarn", "bin", "greprag")
4014
+ path7.join(HOME || "", ".local", "bin", "greprag"),
4015
+ path7.join(HOME || "", ".yarn", "bin", "greprag")
3517
4016
  ];
3518
4017
  for (const c of candidates) {
3519
4018
  try {
3520
- if (c && fs4.existsSync(c))
4019
+ if (c && fs7.existsSync(c))
3521
4020
  return c;
3522
4021
  } catch {
3523
4022
  }
@@ -3526,18 +4025,18 @@ function findGrepragBinary() {
3526
4025
  }
3527
4026
  function tryClaimWatcherLock() {
3528
4027
  try {
3529
- const fd = fs4.openSync(WATCHER_LOCK, "wx");
4028
+ const fd = fs7.openSync(WATCHER_LOCK, "wx");
3530
4029
  return { ok: true, fd };
3531
4030
  } catch (err) {
3532
4031
  if (err.code !== "EEXIST") {
3533
4032
  return { ok: false, reason: "lock-create-failed" };
3534
4033
  }
3535
4034
  try {
3536
- const stat = fs4.statSync(WATCHER_LOCK);
4035
+ const stat = fs7.statSync(WATCHER_LOCK);
3537
4036
  const ageMs = Date.now() - stat.mtimeMs;
3538
4037
  let pidAlive = false;
3539
4038
  try {
3540
- const pid = parseInt(fs4.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
4039
+ const pid = parseInt(fs7.readFileSync(WATCHER_LOCK, "utf-8").trim(), 10);
3541
4040
  pidAlive = pid > 0 && isPidAlive(pid);
3542
4041
  } catch {
3543
4042
  }
@@ -3545,10 +4044,10 @@ function tryClaimWatcherLock() {
3545
4044
  return { ok: false, reason: "busy" };
3546
4045
  }
3547
4046
  try {
3548
- fs4.unlinkSync(WATCHER_LOCK);
4047
+ fs7.unlinkSync(WATCHER_LOCK);
3549
4048
  } catch {
3550
4049
  }
3551
- const fd = fs4.openSync(WATCHER_LOCK, "wx");
4050
+ const fd = fs7.openSync(WATCHER_LOCK, "wx");
3552
4051
  return { ok: true, fd };
3553
4052
  } catch (err2) {
3554
4053
  return { ok: false, reason: "lock-stale-replace-failed" };
@@ -3589,11 +4088,11 @@ function startCaptureWatcher() {
3589
4088
  return;
3590
4089
  }
3591
4090
  try {
3592
- fs4.writeSync(claim.fd, String(process.pid));
3593
- fs4.closeSync(claim.fd);
4091
+ fs7.writeSync(claim.fd, String(process.pid));
4092
+ fs7.closeSync(claim.fd);
3594
4093
  } catch {
3595
4094
  try {
3596
- fs4.unlinkSync(WATCHER_LOCK);
4095
+ fs7.unlinkSync(WATCHER_LOCK);
3597
4096
  } catch {
3598
4097
  }
3599
4098
  return;
@@ -3609,11 +4108,11 @@ function startCaptureWatcher() {
3609
4108
  const claim2 = tryClaimWatcherLock();
3610
4109
  if (claim2.ok) {
3611
4110
  try {
3612
- fs4.writeSync(claim2.fd, String(process.pid));
3613
- fs4.closeSync(claim2.fd);
4111
+ fs7.writeSync(claim2.fd, String(process.pid));
4112
+ fs7.closeSync(claim2.fd);
3614
4113
  } catch {
3615
4114
  try {
3616
- fs4.unlinkSync(WATCHER_LOCK);
4115
+ fs7.unlinkSync(WATCHER_LOCK);
3617
4116
  } catch {
3618
4117
  }
3619
4118
  return false;
@@ -3656,7 +4155,7 @@ function startCaptureWatcher() {
3656
4155
  childStartTime = Date.now();
3657
4156
  dlog(`startCaptureWatcher: spawned 'greprag opencode watch' pid=${child.pid ?? "<none>"}`);
3658
4157
  try {
3659
- fs4.writeFileSync(WATCHER_LOCK, String(child.pid));
4158
+ fs7.writeFileSync(WATCHER_LOCK, String(child.pid));
3660
4159
  } catch {
3661
4160
  }
3662
4161
  child.stderr?.on("data", (chunk2) => {
@@ -3668,9 +4167,9 @@ function startCaptureWatcher() {
3668
4167
  child.on("exit", (code, signal) => {
3669
4168
  dlog(`startCaptureWatcher: child pid=${child.pid ?? "<none>"} exited code=${code} signal=${signal || "none"}`);
3670
4169
  try {
3671
- const current = fs4.readFileSync(WATCHER_LOCK, "utf-8").trim();
4170
+ const current = fs7.readFileSync(WATCHER_LOCK, "utf-8").trim();
3672
4171
  if (current === String(child.pid))
3673
- fs4.unlinkSync(WATCHER_LOCK);
4172
+ fs7.unlinkSync(WATCHER_LOCK);
3674
4173
  } catch {
3675
4174
  }
3676
4175
  currentChild = null;
@@ -3699,7 +4198,7 @@ function startCaptureWatcher() {
3699
4198
  `
3700
4199
  );
3701
4200
  try {
3702
- fs4.unlinkSync(WATCHER_LOCK);
4201
+ fs7.unlinkSync(WATCHER_LOCK);
3703
4202
  } catch {
3704
4203
  }
3705
4204
  return;
@@ -3731,7 +4230,7 @@ function startCaptureWatcher() {
3731
4230
  }, 2e3).unref();
3732
4231
  }
3733
4232
  try {
3734
- fs4.unlinkSync(WATCHER_LOCK);
4233
+ fs7.unlinkSync(WATCHER_LOCK);
3735
4234
  } catch {
3736
4235
  }
3737
4236
  };
@@ -3768,6 +4267,7 @@ async function fetchRecapInbox(anchor) {
3768
4267
  var relayArmedBySession = /* @__PURE__ */ new Set();
3769
4268
  var announcedBySession = /* @__PURE__ */ new Set();
3770
4269
  var turnCountBySession = /* @__PURE__ */ new Map();
4270
+ var procedurePromptKeyBySession = /* @__PURE__ */ new Map();
3771
4271
  var cachedRecap = null;
3772
4272
  var RECAP_CACHE_TTL_MS = 5 * 6e4;
3773
4273
  var cachedSkillRows = null;
@@ -3839,11 +4339,11 @@ function startSessionRelay(sessionId, serverUrl) {
3839
4339
  return;
3840
4340
  }
3841
4341
  try {
3842
- fs4.writeSync(claim.fd, String(process.pid));
3843
- fs4.closeSync(claim.fd);
4342
+ fs7.writeSync(claim.fd, String(process.pid));
4343
+ fs7.closeSync(claim.fd);
3844
4344
  } catch {
3845
4345
  try {
3846
- fs4.unlinkSync(lockPath);
4346
+ fs7.unlinkSync(lockPath);
3847
4347
  } catch {
3848
4348
  }
3849
4349
  return;
@@ -3858,11 +4358,11 @@ function startSessionRelay(sessionId, serverUrl) {
3858
4358
  const claim2 = tryClaimRelayLock(lockPath);
3859
4359
  if (claim2.ok) {
3860
4360
  try {
3861
- fs4.writeSync(claim2.fd, String(process.pid));
3862
- fs4.closeSync(claim2.fd);
4361
+ fs7.writeSync(claim2.fd, String(process.pid));
4362
+ fs7.closeSync(claim2.fd);
3863
4363
  } catch {
3864
4364
  try {
3865
- fs4.unlinkSync(lockPath);
4365
+ fs7.unlinkSync(lockPath);
3866
4366
  } catch {
3867
4367
  }
3868
4368
  return false;
@@ -3898,7 +4398,7 @@ function startSessionRelay(sessionId, serverUrl) {
3898
4398
  childStartTime = Date.now();
3899
4399
  dlog(`startSessionRelay: spawned 'greprag opencode relay' pid=${child.pid ?? "<none>"}`);
3900
4400
  try {
3901
- fs4.writeFileSync(lockPath, String(child.pid));
4401
+ fs7.writeFileSync(lockPath, String(child.pid));
3902
4402
  } catch {
3903
4403
  }
3904
4404
  child.stderr?.on("data", (chunk2) => {
@@ -3909,9 +4409,9 @@ function startSessionRelay(sessionId, serverUrl) {
3909
4409
  });
3910
4410
  child.on("exit", (code, signal) => {
3911
4411
  try {
3912
- const current = fs4.readFileSync(lockPath, "utf-8").trim();
4412
+ const current = fs7.readFileSync(lockPath, "utf-8").trim();
3913
4413
  if (current === String(child.pid))
3914
- fs4.unlinkSync(lockPath);
4414
+ fs7.unlinkSync(lockPath);
3915
4415
  } catch {
3916
4416
  }
3917
4417
  currentChild = null;
@@ -3938,7 +4438,7 @@ function startSessionRelay(sessionId, serverUrl) {
3938
4438
  `
3939
4439
  );
3940
4440
  try {
3941
- fs4.unlinkSync(lockPath);
4441
+ fs7.unlinkSync(lockPath);
3942
4442
  } catch {
3943
4443
  }
3944
4444
  return;
@@ -3970,7 +4470,7 @@ function startSessionRelay(sessionId, serverUrl) {
3970
4470
  }, 2e3).unref();
3971
4471
  }
3972
4472
  try {
3973
- fs4.unlinkSync(lockPath);
4473
+ fs7.unlinkSync(lockPath);
3974
4474
  } catch {
3975
4475
  }
3976
4476
  };
@@ -4030,7 +4530,7 @@ function runPngRecode(input, msgs) {
4030
4530
  }
4031
4531
  function makeCcrStash() {
4032
4532
  return (original, sourceType) => {
4033
- const hash = crypto2.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
4533
+ const hash = crypto5.createHash("sha256").update(original, "utf8").digest("hex").slice(0, 16);
4034
4534
  const bin = grepragBinCached();
4035
4535
  if (bin) {
4036
4536
  try {
@@ -4181,6 +4681,31 @@ ALWAYS pass \`--from-session ${sid82}\` when you run \`greprag send\`, so recipi
4181
4681
  // never opencode's persisted session history. adr:
4182
4682
  // adr/opencode-context-compressor.md
4183
4683
  "experimental.chat.messages.transform": async (input, output) => {
4684
+ try {
4685
+ const prompt = extractLatestOpenCodeProcedurePrompt(output.messages);
4686
+ if (prompt) {
4687
+ const sid = input.sessionID || "<no-session>";
4688
+ if (procedurePromptKeyBySession.get(sid) !== prompt.key) {
4689
+ procedurePromptKeyBySession.set(sid, prompt.key);
4690
+ const sessionContext = input.sessionID ? await resolveSessionContext(input.sessionID) : { anchor: fallbackAnchor, workingDir: fallbackWorkingDir };
4691
+ const procedure = runProcedurePromptCheck({
4692
+ cwd: sessionContext.workingDir,
4693
+ prompt: prompt.prompt,
4694
+ sessionId: input.sessionID
4695
+ });
4696
+ if (procedure.additionalContext) {
4697
+ prompt.parts.push({
4698
+ type: "text",
4699
+ text: procedure.additionalContext,
4700
+ synthetic: true
4701
+ });
4702
+ }
4703
+ dlog(`procedure: ${procedure.mode}${procedure.verb ? ` ${procedure.verb}` : ""} sid=${sid}`);
4704
+ }
4705
+ }
4706
+ } catch (e) {
4707
+ dlog(`messages.transform procedure: swallowed error (turn unaffected): ${e.message}`);
4708
+ }
4184
4709
  try {
4185
4710
  if (pngRecodeEnabled()) {
4186
4711
  const msgs0 = output && output.messages;