@tech-leads-club/harness-toolkit 0.10.1 → 0.10.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.
@@ -27,13 +27,24 @@ export type TriggerContext = {
27
27
  * every repository, or a rule copied between them silently stops firing. An operator who wants their own shape
28
28
  * writes `command(<pattern>)`.
29
29
  */
30
- const SHELL_SHAPES: Record<"pr-open" | "commit" | "push", readonly string[][]> = {
30
+ type ShellShape = {
31
+ readonly prefix: readonly string[];
32
+ /**
33
+ * why this exists only on `gh pr create`: a draft is not yet open for review, so a rule gating `pr-open`
34
+ * has no work to demand proof of. It is also the only way a proof that itself depends on the pull request
35
+ * existing — `gh pr view`, for one — can ever run: open the draft, produce the proof against it, then
36
+ * `gh pr ready`, which keeps its own gate ([/decisions/ad-118.md](/decisions/ad-118.md)).
37
+ */
38
+ readonly excludeIfAny?: readonly string[];
39
+ };
40
+
41
+ const SHELL_SHAPES: Record<"pr-open" | "commit" | "push", readonly ShellShape[]> = {
31
42
  "pr-open": [
32
- ["gh", "pr", "create"],
33
- ["gh", "pr", "ready"],
43
+ { prefix: ["gh", "pr", "create"], excludeIfAny: ["--draft", "-d"] },
44
+ { prefix: ["gh", "pr", "ready"] },
34
45
  ],
35
- commit: [["git", "commit"]],
36
- push: [["git", "push"]],
46
+ commit: [{ prefix: ["git", "commit"] }],
47
+ push: [{ prefix: ["git", "push"] }],
37
48
  };
38
49
 
39
50
  /**
@@ -53,8 +64,15 @@ function subCommands(command: string): string[][] {
53
64
  }
54
65
 
55
66
  /** why prefix rather than equality: `gh pr create --fill --base main` is the same act as `gh pr create`. */
56
- function startsWithShape(words: readonly string[], shape: readonly string[]): boolean {
57
- return shape.every((token, index) => words[index] === token);
67
+ function startsWithShape(words: readonly string[], prefix: readonly string[]): boolean {
68
+ return prefix.every((token, index) => words[index] === token);
69
+ }
70
+
71
+ function matchesShape(words: readonly string[], shape: ShellShape): boolean {
72
+ if (!startsWithShape(words, shape.prefix)) {
73
+ return false;
74
+ }
75
+ return !shape.excludeIfAny?.some((flag) => words.includes(flag));
58
76
  }
59
77
 
60
78
  /**
@@ -82,9 +100,7 @@ export function triggerMatches(trigger: RuleTrigger, context: TriggerContext): b
82
100
  return false;
83
101
  }
84
102
  const shapes = SHELL_SHAPES[trigger.kind];
85
- return subCommands(context.command).some((words) =>
86
- shapes.some((shape) => startsWithShape(words, shape)),
87
- );
103
+ return subCommands(context.command).some((words) => shapes.some((shape) => matchesShape(words, shape)));
88
104
  }
89
105
  default: {
90
106
  if (context.command === undefined) {