@damian87/omp 0.9.2 → 0.12.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.
Files changed (73) hide show
  1. package/.github/copilot-instructions.md +16 -0
  2. package/.github/skills/create-skill/SKILL.md +3 -3
  3. package/.github/skills/daily-log/SKILL.md +1 -1
  4. package/.github/skills/jira-ticket/SKILL.md +6 -3
  5. package/.github/skills/omp-autopilot/SKILL.md +5 -1
  6. package/.github/skills/ralph/SKILL.md +6 -4
  7. package/.github/skills/research-codebase/SKILL.md +10 -6
  8. package/.github/skills/research-codebase/reference/agent-prompts.md +8 -8
  9. package/.github/skills/schedule/SKILL.md +4 -0
  10. package/.github/skills/slack/SKILL.md +1 -0
  11. package/.github/skills/team/SKILL.md +9 -3
  12. package/.github/skills/team/scripts/team-launch.sh +11 -5
  13. package/.github/skills/ultraqa/SKILL.md +5 -2
  14. package/.github/skills/ultrawork/SKILL.md +9 -5
  15. package/.github/skills/weighted-consensus/SKILL.md +8 -3
  16. package/README.md +4 -1
  17. package/dist/src/cli.js +10 -1
  18. package/dist/src/cli.js.map +1 -1
  19. package/dist/src/comms/index.d.ts +13 -6
  20. package/dist/src/comms/index.js +65 -10
  21. package/dist/src/comms/index.js.map +1 -1
  22. package/dist/src/copilot/doctor.d.ts +1 -0
  23. package/dist/src/copilot/doctor.js +242 -8
  24. package/dist/src/copilot/doctor.js.map +1 -1
  25. package/dist/src/copilot/env-passthrough.d.ts +16 -0
  26. package/dist/src/copilot/env-passthrough.js +28 -0
  27. package/dist/src/copilot/env-passthrough.js.map +1 -0
  28. package/dist/src/copilot/launch.js +22 -6
  29. package/dist/src/copilot/launch.js.map +1 -1
  30. package/dist/src/copilot/setup.js +13 -0
  31. package/dist/src/copilot/setup.js.map +1 -1
  32. package/dist/src/copilot/trust.d.ts +22 -0
  33. package/dist/src/copilot/trust.js +62 -0
  34. package/dist/src/copilot/trust.js.map +1 -0
  35. package/dist/src/cost/index.d.ts +3 -0
  36. package/dist/src/cost/index.js +4 -0
  37. package/dist/src/cost/index.js.map +1 -0
  38. package/dist/src/cost/ledger.d.ts +21 -0
  39. package/dist/src/cost/ledger.js +72 -0
  40. package/dist/src/cost/ledger.js.map +1 -0
  41. package/dist/src/cost/summary.d.ts +22 -0
  42. package/dist/src/cost/summary.js +68 -0
  43. package/dist/src/cost/summary.js.map +1 -0
  44. package/dist/src/cost/tokenize.d.ts +7 -0
  45. package/dist/src/cost/tokenize.js +24 -0
  46. package/dist/src/cost/tokenize.js.map +1 -0
  47. package/dist/src/gateway/notify.d.ts +1 -1
  48. package/dist/src/gateway/notify.js +1 -1
  49. package/dist/src/gateway/notify.js.map +1 -1
  50. package/dist/src/instructions-memory.js +7 -6
  51. package/dist/src/instructions-memory.js.map +1 -1
  52. package/dist/src/team/tmux.d.ts +1 -1
  53. package/dist/src/team/tmux.js +41 -10
  54. package/dist/src/team/tmux.js.map +1 -1
  55. package/docs/general-skills.md +1 -0
  56. package/docs/plans/copilot-native-hooks.md +119 -0
  57. package/docs/plans/verification-plan.md +104 -0
  58. package/hooks/hooks.json +59 -72
  59. package/package.json +1 -1
  60. package/plugin.json +1 -1
  61. package/scripts/agent-stop.mjs +87 -0
  62. package/scripts/error.mjs +9 -7
  63. package/scripts/lib/cost-ledger.mjs +91 -0
  64. package/scripts/lib/hook-input.mjs +51 -0
  65. package/scripts/lib/hook-output.mjs +77 -6
  66. package/scripts/lib/loop-driver.mjs +44 -0
  67. package/scripts/lib/minify.mjs +80 -0
  68. package/scripts/post-tool-use-failure.mjs +21 -0
  69. package/scripts/post-tool-use.mjs +71 -8
  70. package/scripts/pre-tool-use.mjs +8 -6
  71. package/scripts/prompt-submit.mjs +14 -9
  72. package/scripts/session-end.mjs +7 -5
  73. package/scripts/session-start.mjs +8 -11
@@ -0,0 +1,80 @@
1
+ import { countTokens } from "./cost-ledger.mjs";
2
+
3
+ const ANSI_RE = /\u001b\[[0-9;]*m/g;
4
+ const DIAGNOSTIC_RE = /\b(fail(?:ed|ure)?|error|exception|assertion|traceback|expected|cannot find|no-undef|TS\d{4})\b/i;
5
+
6
+ function stripAnsi(text) {
7
+ return text.replace(ANSI_RE, "");
8
+ }
9
+
10
+ function dedupConsecutiveLines(lines) {
11
+ const out = [];
12
+ let previous = "";
13
+ let repeated = 0;
14
+ for (const line of lines) {
15
+ if (line === previous) {
16
+ repeated += 1;
17
+ continue;
18
+ }
19
+ if (repeated > 0) out.push(`[omp] repeated previous line ${repeated} time${repeated === 1 ? "" : "s"}`);
20
+ out.push(line);
21
+ previous = line;
22
+ repeated = 0;
23
+ }
24
+ if (repeated > 0) out.push(`[omp] repeated previous line ${repeated} time${repeated === 1 ? "" : "s"}`);
25
+ return out;
26
+ }
27
+
28
+ export function minifyToolOutput(value, options = {}) {
29
+ const rawText = value == null ? "" : String(value);
30
+ const rawTokens = countTokens(rawText);
31
+ const thresholdTokens = options.thresholdTokens ?? 800;
32
+ if (rawTokens <= thresholdTokens) {
33
+ return {
34
+ changed: false,
35
+ text: rawText,
36
+ rawTokens,
37
+ modelTokens: rawTokens,
38
+ savedTokens: 0,
39
+ };
40
+ }
41
+
42
+ const headLines = options.headLines ?? 80;
43
+ const tailLines = options.tailLines ?? 40;
44
+ const normalized = dedupConsecutiveLines(stripAnsi(rawText).split("\n"));
45
+ const omitted = Math.max(0, normalized.length - headLines - tailLines);
46
+ const head = normalized.slice(0, headLines);
47
+ const tail = omitted > 0 ? normalized.slice(-tailLines) : [];
48
+ const tailStart = omitted > 0 ? normalized.length - tailLines : normalized.length;
49
+ const diagnosticLines = normalized
50
+ .map((line, index) => ({ line, index }))
51
+ .filter(({ line, index }) => index >= headLines && index < tailStart && DIAGNOSTIC_RE.test(line))
52
+ .slice(0, options.maxDiagnosticLines ?? 40)
53
+ .map(({ line }) => line);
54
+ const text = [
55
+ `[omp] output trimmed from ${rawTokens} estimated tokens; full raw output is saved on disk.`,
56
+ ...head,
57
+ ...(omitted > 0 ? [`[omp] … omitted ${omitted} middle line${omitted === 1 ? "" : "s"} …`] : []),
58
+ ...(diagnosticLines.length > 0 ? ["[omp] preserved diagnostic lines from omitted output:", ...diagnosticLines] : []),
59
+ ...tail,
60
+ ].join("\n");
61
+ const modelTokens = countTokens(text);
62
+
63
+ if (modelTokens >= rawTokens) {
64
+ return {
65
+ changed: false,
66
+ text: rawText,
67
+ rawTokens,
68
+ modelTokens: rawTokens,
69
+ savedTokens: 0,
70
+ };
71
+ }
72
+
73
+ return {
74
+ changed: true,
75
+ text,
76
+ rawTokens,
77
+ modelTokens,
78
+ savedTokens: rawTokens - modelTokens,
79
+ };
80
+ }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { readStdin } from "./lib/stdin.mjs";
3
+ import { parseHookInput } from "./lib/hook-input.mjs";
4
+ import { appendHookLog, failOpen } from "./lib/hook-output.mjs";
5
+
6
+ const HOOK_NAME = "postToolUseFailure";
7
+
8
+ (async () => {
9
+ try {
10
+ const input = parseHookInput(await readStdin());
11
+ appendHookLog(input.cwd, HOOK_NAME, {
12
+ sessionId: input.sessionId,
13
+ toolName: input.toolName,
14
+ error: input.error ?? "unknown",
15
+ });
16
+ failOpen();
17
+ } catch (err) {
18
+ console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
19
+ failOpen();
20
+ }
21
+ })();
@@ -1,31 +1,94 @@
1
1
  #!/usr/bin/env node
2
- import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { readStdin } from "./lib/stdin.mjs";
5
+ import { buildModifiedResultOutput, failOpen } from "./lib/hook-output.mjs";
6
+ import { parseHookInput } from "./lib/hook-input.mjs";
7
+ import { appendCostRecord, countTokens } from "./lib/cost-ledger.mjs";
8
+ import { minifyToolOutput } from "./lib/minify.mjs";
5
9
 
6
10
  const HOOK_NAME = "PostToolUse";
11
+ const NOISY_COMMAND_RE = /\b(npm|pnpm|yarn|bun|vitest|jest|mocha|pytest|cargo|go|tsc|eslint|biome|ruff|mypy|make|gradle|mvn)\b/i;
12
+
13
+ function safePathPart(value) {
14
+ return (
15
+ String(value || "unknown")
16
+ .replace(/[^a-zA-Z0-9._-]+/g, "-")
17
+ .replace(/^-+|-+$/g, "") || "unknown"
18
+ );
19
+ }
20
+
21
+ function shouldMinify(input) {
22
+ if (process.env.OMP_MINIFY === "0") return false;
23
+ const toolName = String(input.toolName || "").toLowerCase();
24
+ const command = String(input.toolArgs?.command ?? input.toolArgs?.cmd ?? "");
25
+ return (toolName === "bash" || toolName === "shell" || toolName === "terminal") && NOISY_COMMAND_RE.test(command);
26
+ }
7
27
 
8
28
  (async () => {
9
29
  try {
10
30
  const raw = await readStdin();
11
- const data = raw ? JSON.parse(raw) : {};
12
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
13
- const directory = data.directory ?? process.cwd();
14
- const toolName = data.toolName ?? data.tool_name ?? "unknown";
15
- const ok = data.toolOutput != null;
31
+ const input = parseHookInput(raw);
32
+ const sessionId = input.sessionId;
33
+ const directory = input.cwd;
34
+ const toolName = input.toolName;
35
+ const ok = input.toolResult != null;
36
+ const rawText = input.toolResult?.textResultForLlm ?? "";
37
+ const minified = !shouldMinify(input) ? {
38
+ changed: false,
39
+ text: rawText,
40
+ rawTokens: countTokens(rawText),
41
+ modelTokens: countTokens(rawText),
42
+ savedTokens: 0,
43
+ } : minifyToolOutput(rawText);
44
+ let rawPath;
16
45
  const logFile = join(directory, ".omp", "state", "hooks.log");
46
+ let canModifyResult = minified.changed;
17
47
  try {
18
48
  mkdirSync(dirname(logFile), { recursive: true });
19
49
  appendFileSync(
20
50
  logFile,
21
51
  `${JSON.stringify({ ts: new Date().toISOString(), hook: HOOK_NAME, sessionId, toolName, ok })}\n`,
22
52
  );
53
+ if (minified.changed) {
54
+ rawPath = join(
55
+ directory,
56
+ ".omp",
57
+ "state",
58
+ "cost",
59
+ "raw",
60
+ `${safePathPart(sessionId)}-${Date.now()}-${safePathPart(toolName)}.txt`,
61
+ );
62
+ mkdirSync(dirname(rawPath), { recursive: true });
63
+ writeFileSync(rawPath, rawText, "utf8");
64
+ }
65
+ } catch {
66
+ canModifyResult = false;
67
+ }
68
+ try {
69
+ appendCostRecord(directory, {
70
+ sessionId,
71
+ event: "postToolUse",
72
+ toolName,
73
+ inTokens: countTokens(input.toolArgs),
74
+ outTokens: canModifyResult ? minified.modelTokens : minified.rawTokens,
75
+ rawOutTokens: minified.rawTokens,
76
+ savedTokens: canModifyResult ? minified.savedTokens : 0,
77
+ rawPath,
78
+ });
23
79
  } catch {
24
80
  // best effort
25
81
  }
26
- console.log(JSON.stringify({ continue: true }));
82
+ if (canModifyResult) {
83
+ console.log(JSON.stringify(buildModifiedResultOutput(
84
+ minified.text,
85
+ `[omp] output trimmed ${minified.rawTokens}→${minified.modelTokens} tokens; full output at ${rawPath}`,
86
+ )));
87
+ return;
88
+ }
89
+ failOpen();
27
90
  } catch (err) {
28
91
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
29
- console.log(JSON.stringify({ continue: true }));
92
+ failOpen();
30
93
  }
31
94
  })();
@@ -2,16 +2,18 @@
2
2
  import { appendFileSync, mkdirSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { readStdin } from "./lib/stdin.mjs";
5
+ import { failOpen } from "./lib/hook-output.mjs";
6
+ import { parseHookInput } from "./lib/hook-input.mjs";
5
7
 
6
8
  const HOOK_NAME = "PreToolUse";
7
9
 
8
10
  (async () => {
9
11
  try {
10
12
  const raw = await readStdin();
11
- const data = raw ? JSON.parse(raw) : {};
12
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
13
- const directory = data.directory ?? process.cwd();
14
- const toolName = data.toolName ?? data.tool_name ?? "unknown";
13
+ const input = parseHookInput(raw);
14
+ const sessionId = input.sessionId;
15
+ const directory = input.cwd;
16
+ const toolName = input.toolName;
15
17
  const logFile = join(directory, ".omp", "state", "hooks.log");
16
18
  try {
17
19
  mkdirSync(dirname(logFile), { recursive: true });
@@ -22,9 +24,9 @@ const HOOK_NAME = "PreToolUse";
22
24
  } catch {
23
25
  // best effort
24
26
  }
25
- console.log(JSON.stringify({ continue: true }));
27
+ failOpen();
26
28
  } catch (err) {
27
29
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
28
- console.log(JSON.stringify({ continue: true }));
30
+ failOpen();
29
31
  }
30
32
  })();
@@ -2,8 +2,11 @@
2
2
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { readStdin } from "./lib/stdin.mjs";
5
+ import { failOpen, printContinue } from "./lib/hook-output.mjs";
5
6
  import { recordPrompt } from "./lib/daily-log.mjs";
6
7
  import { ompRoot } from "./lib/omp-root.mjs";
8
+ import { parseHookInput } from "./lib/hook-input.mjs";
9
+ import { appendCostRecord, countTokens } from "./lib/cost-ledger.mjs";
7
10
 
8
11
  const HOOK_NAME = "UserPromptSubmit";
9
12
 
@@ -51,11 +54,16 @@ function appendLog(directory, payload) {
51
54
  (async () => {
52
55
  try {
53
56
  const raw = await readStdin();
54
- const data = raw ? JSON.parse(raw) : {};
55
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
56
- const directory = data.directory ?? process.cwd();
57
- const prompt = data.prompt ?? data.message?.content ?? "";
57
+ const input = parseHookInput(raw);
58
+ const sessionId = input.sessionId;
59
+ const directory = input.cwd;
60
+ const prompt = input.prompt;
58
61
  appendLog(directory, { sessionId, promptBytes: String(prompt).length });
62
+ appendCostRecord(directory, {
63
+ sessionId,
64
+ event: "userPromptSubmitted",
65
+ inTokens: countTokens(prompt),
66
+ });
59
67
  // Count this prompt as session work (signals the SessionEnd nudge logic).
60
68
  // Injects nothing — keeps per-turn token cost at zero.
61
69
  try {
@@ -67,12 +75,9 @@ function appendLog(directory, payload) {
67
75
  const cont = buildContinuationContext(directory);
68
76
  if (cont) parts.push(cont);
69
77
  const additionalContext = parts.join("\n\n---\n\n");
70
- const output = additionalContext
71
- ? { continue: true, hookSpecificOutput: { hookEventName: HOOK_NAME, additionalContext } }
72
- : { continue: true };
73
- console.log(JSON.stringify(output));
78
+ printContinue(HOOK_NAME, additionalContext);
74
79
  } catch (err) {
75
80
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
76
- console.log(JSON.stringify({ continue: true }));
81
+ failOpen();
77
82
  }
78
83
  })();
@@ -4,15 +4,17 @@ import { dirname, join } from "node:path";
4
4
  import { readStdin } from "./lib/stdin.mjs";
5
5
  import { endSession } from "./lib/daily-log.mjs";
6
6
  import { ompRoot } from "./lib/omp-root.mjs";
7
+ import { failOpen } from "./lib/hook-output.mjs";
8
+ import { parseHookInput } from "./lib/hook-input.mjs";
7
9
 
8
10
  const HOOK_NAME = "SessionEnd";
9
11
 
10
12
  (async () => {
11
13
  try {
12
14
  const raw = await readStdin();
13
- const data = raw ? JSON.parse(raw) : {};
14
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
15
- const directory = data.directory ?? process.cwd();
15
+ const input = parseHookInput(raw);
16
+ const sessionId = input.sessionId;
17
+ const directory = input.cwd;
16
18
  const logFile = join(ompRoot(directory), ".omp", "state", "hooks.log");
17
19
  try {
18
20
  mkdirSync(dirname(logFile), { recursive: true });
@@ -26,9 +28,9 @@ const HOOK_NAME = "SessionEnd";
26
28
  // Arm a daily-log nudge for the next session if this one did work but
27
29
  // logged nothing. endSession never throws.
28
30
  endSession(directory);
29
- console.log(JSON.stringify({ continue: true }));
31
+ failOpen();
30
32
  } catch (err) {
31
33
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
32
- console.log(JSON.stringify({ continue: true }));
34
+ failOpen();
33
35
  }
34
36
  })();
@@ -2,11 +2,13 @@
2
2
  import { appendFileSync, mkdirSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { readStdin } from "./lib/stdin.mjs";
5
+ import { failOpen, printContinue } from "./lib/hook-output.mjs";
5
6
  import { checkForUpdate, formatUpdateNotice } from "./lib/version-check.mjs";
6
7
  import { scanScheduleResults } from "./lib/schedule-results.mjs";
7
8
  import { readRepoGoal, readTodayGoal, recentEntryStats, startSession } from "./lib/daily-log.mjs";
8
9
  import { readDirectives } from "./lib/project-memory.mjs";
9
10
  import { ompRoot } from "./lib/omp-root.mjs";
11
+ import { parseHookInput } from "./lib/hook-input.mjs";
10
12
 
11
13
  const HOOK_NAME = "SessionStart";
12
14
 
@@ -19,7 +21,7 @@ function buildDailyLogBreadcrumb(directory) {
19
21
  if (goal) lines.push(`Goal: ${goal}`);
20
22
  if (entries > 0)
21
23
  lines.push(
22
- `${entries} ${entries === 1 ? "entry" : "entries"} logged in the last 7 days — run \`omp daily-log read\` to load if relevant.`,
24
+ `${entries} ${entries === 1 ? "entry" : "entries"} logged in the last 7 days — run \`omp daily-log read --days 7\` to load if relevant.`,
23
25
  );
24
26
  return lines.join("\n");
25
27
  } catch {
@@ -30,9 +32,9 @@ function buildDailyLogBreadcrumb(directory) {
30
32
  (async () => {
31
33
  try {
32
34
  const raw = await readStdin();
33
- const data = raw ? JSON.parse(raw) : {};
34
- const sessionId = data.sessionId ?? data.session_id ?? "unknown";
35
- const directory = data.directory ?? process.cwd();
35
+ const input = parseHookInput(raw);
36
+ const sessionId = input.sessionId;
37
+ const directory = input.cwd;
36
38
  const stateDir = join(ompRoot(directory), ".omp", "state");
37
39
  const logFile = join(stateDir, "hooks.log");
38
40
  mkdirSync(dirname(logFile), { recursive: true });
@@ -84,14 +86,9 @@ function buildDailyLogBreadcrumb(directory) {
84
86
  if (flush) parts.push(`[DAILY LOG] ${flush}`);
85
87
  const additionalContext = parts.join("\n\n---\n\n");
86
88
 
87
- console.log(
88
- JSON.stringify({
89
- continue: true,
90
- hookSpecificOutput: { hookEventName: HOOK_NAME, additionalContext },
91
- }),
92
- );
89
+ printContinue(HOOK_NAME, additionalContext);
93
90
  } catch (err) {
94
91
  console.error(`[hook ${HOOK_NAME}] failed: ${err?.message ?? err}`);
95
- console.log(JSON.stringify({ continue: true }));
92
+ failOpen();
96
93
  }
97
94
  })();