@cad0p/pi-steering 0.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.
Files changed (246) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +29 -0
  3. package/README.md +920 -0
  4. package/dist/__test-helpers__.d.ts +95 -0
  5. package/dist/__test-helpers__.d.ts.map +1 -0
  6. package/dist/__test-helpers__.js +174 -0
  7. package/dist/__test-helpers__.js.map +1 -0
  8. package/dist/bin/pi-steering.d.ts +9 -0
  9. package/dist/bin/pi-steering.d.ts.map +1 -0
  10. package/dist/bin/pi-steering.js +506 -0
  11. package/dist/bin/pi-steering.js.map +1 -0
  12. package/dist/compat.d.ts +50 -0
  13. package/dist/compat.d.ts.map +1 -0
  14. package/dist/compat.js +180 -0
  15. package/dist/compat.js.map +1 -0
  16. package/dist/defaults.d.ts +61 -0
  17. package/dist/defaults.d.ts.map +1 -0
  18. package/dist/defaults.js +106 -0
  19. package/dist/defaults.js.map +1 -0
  20. package/dist/define-config.d.ts +235 -0
  21. package/dist/define-config.d.ts.map +1 -0
  22. package/dist/define-config.js +118 -0
  23. package/dist/define-config.js.map +1 -0
  24. package/dist/define-predicate.d.ts +49 -0
  25. package/dist/define-predicate.d.ts.map +1 -0
  26. package/dist/define-predicate.js +35 -0
  27. package/dist/define-predicate.js.map +1 -0
  28. package/dist/evaluator-internals/context.d.ts +186 -0
  29. package/dist/evaluator-internals/context.d.ts.map +1 -0
  30. package/dist/evaluator-internals/context.js +219 -0
  31. package/dist/evaluator-internals/context.js.map +1 -0
  32. package/dist/evaluator-internals/override.d.ts +19 -0
  33. package/dist/evaluator-internals/override.d.ts.map +1 -0
  34. package/dist/evaluator-internals/override.js +79 -0
  35. package/dist/evaluator-internals/override.js.map +1 -0
  36. package/dist/evaluator-internals/predicates.d.ts +152 -0
  37. package/dist/evaluator-internals/predicates.d.ts.map +1 -0
  38. package/dist/evaluator-internals/predicates.js +849 -0
  39. package/dist/evaluator-internals/predicates.js.map +1 -0
  40. package/dist/evaluator-internals/speculative-synthesis.d.ts +102 -0
  41. package/dist/evaluator-internals/speculative-synthesis.d.ts.map +1 -0
  42. package/dist/evaluator-internals/speculative-synthesis.js +101 -0
  43. package/dist/evaluator-internals/speculative-synthesis.js.map +1 -0
  44. package/dist/evaluator.d.ts +101 -0
  45. package/dist/evaluator.d.ts.map +1 -0
  46. package/dist/evaluator.js +714 -0
  47. package/dist/evaluator.js.map +1 -0
  48. package/dist/helpers/walker-unknown-cwd-reason.d.ts +68 -0
  49. package/dist/helpers/walker-unknown-cwd-reason.d.ts.map +1 -0
  50. package/dist/helpers/walker-unknown-cwd-reason.js +73 -0
  51. package/dist/helpers/walker-unknown-cwd-reason.js.map +1 -0
  52. package/dist/index.d.ts +42 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +93 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/internal/drop-unused-observers.d.ts +43 -0
  57. package/dist/internal/drop-unused-observers.d.ts.map +1 -0
  58. package/dist/internal/drop-unused-observers.js +50 -0
  59. package/dist/internal/drop-unused-observers.js.map +1 -0
  60. package/dist/internal/finalize-plugin-state.d.ts +22 -0
  61. package/dist/internal/finalize-plugin-state.d.ts.map +1 -0
  62. package/dist/internal/finalize-plugin-state.js +14 -0
  63. package/dist/internal/finalize-plugin-state.js.map +1 -0
  64. package/dist/internal/merge-observers.d.ts +30 -0
  65. package/dist/internal/merge-observers.d.ts.map +1 -0
  66. package/dist/internal/merge-observers.js +27 -0
  67. package/dist/internal/merge-observers.js.map +1 -0
  68. package/dist/internal/pattern-utils.d.ts +33 -0
  69. package/dist/internal/pattern-utils.d.ts.map +1 -0
  70. package/dist/internal/pattern-utils.js +20 -0
  71. package/dist/internal/pattern-utils.js.map +1 -0
  72. package/dist/internal/ref-text.d.ts +11 -0
  73. package/dist/internal/ref-text.d.ts.map +1 -0
  74. package/dist/internal/ref-text.js +15 -0
  75. package/dist/internal/ref-text.js.map +1 -0
  76. package/dist/internal/session-runtime.d.ts +44 -0
  77. package/dist/internal/session-runtime.d.ts.map +1 -0
  78. package/dist/internal/session-runtime.js +143 -0
  79. package/dist/internal/session-runtime.js.map +1 -0
  80. package/dist/internal/watch-matcher.d.ts +56 -0
  81. package/dist/internal/watch-matcher.d.ts.map +1 -0
  82. package/dist/internal/watch-matcher.js +179 -0
  83. package/dist/internal/watch-matcher.js.map +1 -0
  84. package/dist/loader.d.ts +97 -0
  85. package/dist/loader.d.ts.map +1 -0
  86. package/dist/loader.js +527 -0
  87. package/dist/loader.js.map +1 -0
  88. package/dist/observer-dispatcher.d.ts +69 -0
  89. package/dist/observer-dispatcher.d.ts.map +1 -0
  90. package/dist/observer-dispatcher.js +184 -0
  91. package/dist/observer-dispatcher.js.map +1 -0
  92. package/dist/plugin-merger.d.ts +175 -0
  93. package/dist/plugin-merger.d.ts.map +1 -0
  94. package/dist/plugin-merger.js +450 -0
  95. package/dist/plugin-merger.js.map +1 -0
  96. package/dist/plugins/git/branch-tracker.d.ts +85 -0
  97. package/dist/plugins/git/branch-tracker.d.ts.map +1 -0
  98. package/dist/plugins/git/branch-tracker.js +181 -0
  99. package/dist/plugins/git/branch-tracker.js.map +1 -0
  100. package/dist/plugins/git/cwd-extensions.d.ts +10 -0
  101. package/dist/plugins/git/cwd-extensions.d.ts.map +1 -0
  102. package/dist/plugins/git/cwd-extensions.js +155 -0
  103. package/dist/plugins/git/cwd-extensions.js.map +1 -0
  104. package/dist/plugins/git/git-ops.d.ts +89 -0
  105. package/dist/plugins/git/git-ops.d.ts.map +1 -0
  106. package/dist/plugins/git/git-ops.js +107 -0
  107. package/dist/plugins/git/git-ops.js.map +1 -0
  108. package/dist/plugins/git/index.d.ts +225 -0
  109. package/dist/plugins/git/index.d.ts.map +1 -0
  110. package/dist/plugins/git/index.js +52 -0
  111. package/dist/plugins/git/index.js.map +1 -0
  112. package/dist/plugins/git/predicates.d.ts +312 -0
  113. package/dist/plugins/git/predicates.d.ts.map +1 -0
  114. package/dist/plugins/git/predicates.js +580 -0
  115. package/dist/plugins/git/predicates.js.map +1 -0
  116. package/dist/plugins/git/rules.d.ts +209 -0
  117. package/dist/plugins/git/rules.d.ts.map +1 -0
  118. package/dist/plugins/git/rules.js +267 -0
  119. package/dist/plugins/git/rules.js.map +1 -0
  120. package/dist/schema.d.ts +1799 -0
  121. package/dist/schema.d.ts.map +1 -0
  122. package/dist/schema.js +4 -0
  123. package/dist/schema.js.map +1 -0
  124. package/dist/testing/index.d.ts +621 -0
  125. package/dist/testing/index.d.ts.map +1 -0
  126. package/dist/testing/index.js +747 -0
  127. package/dist/testing/index.js.map +1 -0
  128. package/examples/README.md +83 -0
  129. package/examples/combined-git-discipline/README.md +52 -0
  130. package/examples/combined-git-discipline/node_modules/.bin/pi-steering +43 -0
  131. package/examples/combined-git-discipline/package.json +18 -0
  132. package/examples/combined-git-discipline/steering.json +27 -0
  133. package/examples/combined-git-discipline/steering.test.ts +42 -0
  134. package/examples/combined-git-discipline/steering.ts +64 -0
  135. package/examples/combined-git-discipline/tsconfig.json +7 -0
  136. package/examples/draft-prs-only/README.md +42 -0
  137. package/examples/draft-prs-only/node_modules/.bin/pi-steering +43 -0
  138. package/examples/draft-prs-only/package.json +18 -0
  139. package/examples/draft-prs-only/steering.json +12 -0
  140. package/examples/draft-prs-only/steering.test.ts +48 -0
  141. package/examples/draft-prs-only/steering.ts +36 -0
  142. package/examples/draft-prs-only/tsconfig.json +7 -0
  143. package/examples/dynamic-reason-runtime-cwd/README.md +148 -0
  144. package/examples/dynamic-reason-runtime-cwd/node_modules/.bin/pi-steering +43 -0
  145. package/examples/dynamic-reason-runtime-cwd/package.json +18 -0
  146. package/examples/dynamic-reason-runtime-cwd/steering.test.ts +130 -0
  147. package/examples/dynamic-reason-runtime-cwd/steering.ts +82 -0
  148. package/examples/dynamic-reason-runtime-cwd/tsconfig.json +7 -0
  149. package/examples/force-push-strict/README.md +75 -0
  150. package/examples/force-push-strict/node_modules/.bin/pi-steering +43 -0
  151. package/examples/force-push-strict/package.json +18 -0
  152. package/examples/force-push-strict/steering.json +12 -0
  153. package/examples/force-push-strict/steering.test.ts +51 -0
  154. package/examples/force-push-strict/steering.ts +54 -0
  155. package/examples/force-push-strict/tsconfig.json +7 -0
  156. package/examples/no-amend/README.md +52 -0
  157. package/examples/no-amend/node_modules/.bin/pi-steering +43 -0
  158. package/examples/no-amend/package.json +18 -0
  159. package/examples/no-amend/steering.cwd-scoped.json +12 -0
  160. package/examples/no-amend/steering.json +11 -0
  161. package/examples/no-amend/steering.test.ts +49 -0
  162. package/examples/no-amend/steering.ts +38 -0
  163. package/examples/no-amend/tsconfig.json +7 -0
  164. package/examples/work-item-plugin/README.md +103 -0
  165. package/examples/work-item-plugin/node_modules/.bin/pi-steering +43 -0
  166. package/examples/work-item-plugin/package.json +19 -0
  167. package/examples/work-item-plugin/src/index.test.ts +168 -0
  168. package/examples/work-item-plugin/src/index.ts +122 -0
  169. package/examples/work-item-plugin/src/observers/npm-test-tracker.test.ts +95 -0
  170. package/examples/work-item-plugin/src/observers/npm-test-tracker.ts +88 -0
  171. package/examples/work-item-plugin/src/observers/retest-required-tracker.test.ts +81 -0
  172. package/examples/work-item-plugin/src/observers/retest-required-tracker.ts +81 -0
  173. package/examples/work-item-plugin/src/predicates/work-item-format.test.ts +157 -0
  174. package/examples/work-item-plugin/src/predicates/work-item-format.ts +99 -0
  175. package/examples/work-item-plugin/src/rules/commit-description-check.test.ts +107 -0
  176. package/examples/work-item-plugin/src/rules/commit-description-check.ts +83 -0
  177. package/examples/work-item-plugin/src/rules/commit-requires-work-item.test.ts +79 -0
  178. package/examples/work-item-plugin/src/rules/commit-requires-work-item.ts +56 -0
  179. package/examples/work-item-plugin/src/rules/push-requires-tests.test.ts +245 -0
  180. package/examples/work-item-plugin/src/rules/push-requires-tests.ts +62 -0
  181. package/examples/work-item-plugin/tsconfig.json +8 -0
  182. package/package.json +88 -0
  183. package/skills/steering-authoring/SKILL.md +102 -0
  184. package/src/INVARIANTS.md +116 -0
  185. package/src/__test-helpers__.ts +243 -0
  186. package/src/bin/pi-steering.test.ts +920 -0
  187. package/src/bin/pi-steering.ts +584 -0
  188. package/src/compat.test.ts +431 -0
  189. package/src/compat.ts +282 -0
  190. package/src/defaults.test.ts +585 -0
  191. package/src/defaults.ts +148 -0
  192. package/src/define-config.test.ts +888 -0
  193. package/src/define-config.ts +375 -0
  194. package/src/define-predicate.test.ts +70 -0
  195. package/src/define-predicate.ts +57 -0
  196. package/src/evaluator-internals/context.ts +290 -0
  197. package/src/evaluator-internals/override.test.ts +99 -0
  198. package/src/evaluator-internals/override.ts +83 -0
  199. package/src/evaluator-internals/predicates.ts +1037 -0
  200. package/src/evaluator-internals/speculative-synthesis.test.ts +411 -0
  201. package/src/evaluator-internals/speculative-synthesis.ts +199 -0
  202. package/src/evaluator.test.ts +6296 -0
  203. package/src/evaluator.ts +1106 -0
  204. package/src/factory-time-load.test.ts +630 -0
  205. package/src/helpers/walker-unknown-cwd-reason.test.ts +73 -0
  206. package/src/helpers/walker-unknown-cwd-reason.ts +83 -0
  207. package/src/index.test.ts +836 -0
  208. package/src/index.ts +229 -0
  209. package/src/internal/drop-unused-observers.test.ts +141 -0
  210. package/src/internal/drop-unused-observers.ts +81 -0
  211. package/src/internal/finalize-plugin-state.ts +43 -0
  212. package/src/internal/merge-observers.ts +51 -0
  213. package/src/internal/pattern-utils.test.ts +52 -0
  214. package/src/internal/pattern-utils.ts +38 -0
  215. package/src/internal/ref-text.test.ts +54 -0
  216. package/src/internal/ref-text.ts +17 -0
  217. package/src/internal/session-runtime.test.ts +629 -0
  218. package/src/internal/session-runtime.ts +201 -0
  219. package/src/internal/watch-matcher.test.ts +326 -0
  220. package/src/internal/watch-matcher.ts +200 -0
  221. package/src/jsdoc-propagation.test.ts +277 -0
  222. package/src/loader.test.ts +1098 -0
  223. package/src/loader.ts +581 -0
  224. package/src/not-block-onunknown.test.ts +554 -0
  225. package/src/observer-dispatcher.test.ts +939 -0
  226. package/src/observer-dispatcher.ts +290 -0
  227. package/src/per-predicate-typing.test.ts +361 -0
  228. package/src/plugin-merger.test.ts +790 -0
  229. package/src/plugin-merger.ts +598 -0
  230. package/src/plugins/git/README.md +485 -0
  231. package/src/plugins/git/branch-tracker.test.ts +281 -0
  232. package/src/plugins/git/branch-tracker.ts +187 -0
  233. package/src/plugins/git/cwd-extensions.test.ts +230 -0
  234. package/src/plugins/git/cwd-extensions.ts +160 -0
  235. package/src/plugins/git/git-ops.test.ts +397 -0
  236. package/src/plugins/git/git-ops.ts +173 -0
  237. package/src/plugins/git/index.ts +253 -0
  238. package/src/plugins/git/integration.test.ts +705 -0
  239. package/src/plugins/git/predicates.test.ts +1401 -0
  240. package/src/plugins/git/predicates.ts +693 -0
  241. package/src/plugins/git/rules.test.ts +1001 -0
  242. package/src/plugins/git/rules.ts +307 -0
  243. package/src/schema.test.ts +259 -0
  244. package/src/schema.ts +1963 -0
  245. package/src/testing/index.test.ts +1696 -0
  246. package/src/testing/index.ts +1472 -0
@@ -0,0 +1,181 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * `branch` tracker for the git plugin.
5
+ *
6
+ * Models the effective git branch AT EACH extracted command within a
7
+ * single `tool_call`. The motivating case is the ADR's
8
+ * `git checkout A && git commit` example: a session-level
9
+ * "current branch" query would miss the mid-command checkout and allow
10
+ * the commit on what looks like a non-protected branch. A walker-backed
11
+ * tracker folds the `git checkout` delta into the branch value seen by
12
+ * the subsequent `git commit`, so a rule gated on
13
+ * `when: { branch: /^main$/ }` correctly fires if the chain ends up on
14
+ * `main`.
15
+ *
16
+ * Semantics:
17
+ * - `git checkout X` - sequential; branch becomes `X` for the rest
18
+ * of this scope.
19
+ * - `git switch X` - sequential; same as above.
20
+ * - `git checkout -b NEW` / `git switch -c NEW` - sequential; branch
21
+ * becomes `NEW`. The `-b` / `-c` token is consumed; the following
22
+ * argument is the new branch name.
23
+ * - Anything else under `git` (including `git commit`, `git status`,
24
+ * `git checkout -- FILE`) leaves the branch unchanged. Returning
25
+ * `current` (not `undefined`) is important: a `git commit` that
26
+ * happens to carry a non-static arg (e.g. `-m "$MSG"`) must NOT
27
+ * collapse branch state to `unknown`.
28
+ * - Non-static branch names (`git checkout $BR`, `git checkout "$BR"`)
29
+ * return `undefined`, which the walker translates to the tracker's
30
+ * `unknown` sentinel. Predicates then apply their `onUnknown`
31
+ * policy (default `"block"` - see the `branch` predicate handler).
32
+ *
33
+ * Subshells: `isolated` - a `(git checkout X)` inside parens cannot
34
+ * change the enclosing shell's branch (real git semantics - the
35
+ * subshell has no effect on the parent's working tree state for this
36
+ * dimension).
37
+ *
38
+ * The tracker distinguishes two "I don't know the branch" states so
39
+ * predicates can apply the correct policy:
40
+ *
41
+ * - `initial: NO_CHECKOUT_IN_CHAIN` - no branch-changing modifier
42
+ * has fired in this ref's scope. The plugin cannot synchronously
43
+ * know the session's current branch at construction time; the
44
+ * `branch` predicate handler shells out via `ctx.exec("git",
45
+ * ["branch", "--show-current"])` to learn it. (A session-start
46
+ * prefetch is a reasonable future optimization but out of scope
47
+ * here.)
48
+ * - `unknown: "unknown"` - a modifier FIRED but couldn't resolve
49
+ * statically (e.g. `git checkout $VAR`). The predicate must NOT
50
+ * shell out in this case: `git branch --show-current` would
51
+ * return the PRE-checkout branch and silently defeat the
52
+ * walker's tracking. Predicates apply their `onUnknown` policy
53
+ * instead (default `"block"` -> fail-closed).
54
+ *
55
+ * The `NO_CHECKOUT_IN_CHAIN` sentinel is chosen to be a string that
56
+ * cannot occur as a real git branch name (git refuses refs containing
57
+ * `:`). Exported so tests and plugin authors can reference it; it
58
+ * should never be constructed ad-hoc.
59
+ *
60
+ * ## Note for plugin authors
61
+ *
62
+ * This tracker is a canonical example of the strict Tracker contract -
63
+ * unresolvable modifier targets return `undefined`, NOT `current`. The
64
+ * built-in `cwdTracker.cd` modifier is a documented Phase-1 exception;
65
+ * do not copy its "return current" shortcut in new trackers.
66
+ */
67
+ import { isStaticallyResolvable, } from "@cad0p/unbash-walker";
68
+ /**
69
+ * Sentinel value for `branchTracker.initial` - marks "no branch-
70
+ * changing modifier has fired in this ref's scope yet". Distinct
71
+ * from the `"unknown"` sentinel (which marks "a modifier DID fire
72
+ * but couldn't resolve statically"). See the file JSDoc for why the
73
+ * distinction matters to the `branch` predicate's exec fallback
74
+ * decision. The colon is chosen because git rejects it in branch
75
+ * names, so this value cannot collide with a real branch.
76
+ */
77
+ export const NO_CHECKOUT_IN_CHAIN = "pi-steering:no-checkout-in-chain";
78
+ /** Branch-changing git subcommands. */
79
+ const CHECKOUT_SUBCOMMANDS = new Set(["checkout", "switch"]);
80
+ /** `git checkout -b NEW` / `git switch -c NEW` - new-branch flags. */
81
+ const NEW_BRANCH_FLAGS = new Set(["-b", "-c"]);
82
+ /**
83
+ * Sequential modifier that applies to every `git`-basename command.
84
+ *
85
+ * Returns the new branch for `checkout` / `switch` invocations;
86
+ * returns `current` unchanged for every other git subcommand (so
87
+ * `git commit -m "x"` does NOT collapse the branch to `unknown`);
88
+ * returns `undefined` when the target branch name can't be resolved
89
+ * statically.
90
+ *
91
+ * Pre-subcommand flags are skipped before we look at the subcommand
92
+ * token. Git accepts `git -C PATH`, `git -c KEY=VAL`, `git --no-pager`,
93
+ * `git --git-dir=/x`, `git --work-tree=/y`, `git --paginate`, etc.
94
+ * before the subcommand. Without this skip, `git -C /other checkout
95
+ * feat` would look at the `-C` flag as the subcommand, miss the
96
+ * checkout, and silently bypass `no-main-commit`. This mirrors the
97
+ * `applyGitCwd` flag scanner in `unbash-walker/src/trackers/cwd.ts`:
98
+ * only `-C <path>` and `-c <key=value>` consume an additional token;
99
+ * every other flag form (short cluster, long with or without attached
100
+ * value) is a single token.
101
+ */
102
+ const gitBranchModifier = {
103
+ scope: "sequential",
104
+ apply: (args, current) => {
105
+ // Scan past pre-subcommand flags to find the real subcommand.
106
+ // Known value-consuming flags: `-C <path>`, `-c <key=val>`.
107
+ // Others (`--no-pager`, `--git-dir=/x`, `--work-tree=/y`,
108
+ // `--paginate`, `-p`, etc.) are single tokens.
109
+ let i = 0;
110
+ while (i < args.length) {
111
+ const tokWord = args[i];
112
+ const tok = tokWord?.value ?? tokWord?.text ?? "";
113
+ if (!tok.startsWith("-"))
114
+ break; // found the subcommand
115
+ if (tok === "-C" || tok === "-c") {
116
+ i += 2;
117
+ continue;
118
+ }
119
+ i++; // --long-flag, --long=value, or -shortcluster
120
+ }
121
+ // No subcommand - e.g. bare `git` or `git --help`. Branch unchanged.
122
+ const subcmdWord = args[i];
123
+ if (!subcmdWord)
124
+ return current;
125
+ const subcmd = subcmdWord.value ?? subcmdWord.text;
126
+ if (!subcmd || !CHECKOUT_SUBCOMMANDS.has(subcmd))
127
+ return current;
128
+ // `git checkout` / `git switch` with no further args: also a
129
+ // help / error case. Leave branch alone.
130
+ const firstArgWord = args[i + 1];
131
+ if (!firstArgWord)
132
+ return current;
133
+ const firstArg = firstArgWord.value ?? firstArgWord.text;
134
+ if (firstArg === undefined)
135
+ return current;
136
+ // `git checkout --help` / `git checkout -h` - help request, not a
137
+ // branch change. Every other unrecognised flag form (e.g. `--`, `.`,
138
+ // `--force`) still flows through the default branch-name path and
139
+ // is accepted as a documented false-positive (see test cases).
140
+ if (firstArg === "--help" || firstArg === "-h")
141
+ return current;
142
+ // `git checkout -b NEW` / `git switch -c NEW` - the branch
143
+ // argument is at position i + 2.
144
+ if (NEW_BRANCH_FLAGS.has(firstArg)) {
145
+ const newBranchWord = args[i + 2];
146
+ if (!newBranchWord)
147
+ return current;
148
+ if (!isStaticallyResolvable(newBranchWord))
149
+ return undefined;
150
+ const newBranch = newBranchWord.value ?? newBranchWord.text;
151
+ return newBranch === undefined ? current : newBranch;
152
+ }
153
+ // Plain `git checkout X` / `git switch X`. Note: `git checkout --
154
+ // FILE` (restore path) is NOT a branch change, but the first arg
155
+ // `--` happens to be statically resolvable AND `!= undefined`.
156
+ // That's a false-positive corner we accept for Phase 4 - agents
157
+ // rarely emit the `--` separator form, and the alternative
158
+ // (hard-coding a `--` check) brittles against other pathspec
159
+ // conventions. Follow-up if it bites.
160
+ if (!isStaticallyResolvable(firstArgWord))
161
+ return undefined;
162
+ return firstArg;
163
+ },
164
+ };
165
+ /**
166
+ * The branch tracker.
167
+ *
168
+ * Registered by the git plugin under `trackers.branch`. Walker-merged
169
+ * with any future plugin wanting to extend branch semantics (though
170
+ * the tracker name `branch` is expected to stay owned by the git
171
+ * plugin - name collisions are a hard error per the plugin-merger).
172
+ */
173
+ export const branchTracker = {
174
+ initial: NO_CHECKOUT_IN_CHAIN,
175
+ unknown: "unknown",
176
+ modifiers: {
177
+ git: gitBranchModifier,
178
+ },
179
+ subshellSemantics: "isolated",
180
+ };
181
+ //# sourceMappingURL=branch-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-tracker.js","sourceRoot":"","sources":["../../../src/plugins/git/branch-tracker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type Modifier } from "@cad0p/unbash-walker";
2
+ /**
3
+ * Modifiers the git plugin registers under `cwd.git`. Exposed as an
4
+ * array so the plugin merger sees each entry distinctly; the runtime
5
+ * could today also accept a single modifier, but the array form
6
+ * leaves room for future git-specific cwd tweaks without changing
7
+ * the plugin manifest.
8
+ */
9
+ export declare const gitCwdExtensions: readonly Modifier<unknown>[];
10
+ //# sourceMappingURL=cwd-extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cwd-extensions.d.ts","sourceRoot":"","sources":["../../../src/plugins/git/cwd-extensions.ts"],"names":[],"mappings":"AAqFA,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAiE7E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,QAAQ,CAAC,OAAO,CAAC,EAExD,CAAC"}
@@ -0,0 +1,155 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * `cwd` tracker extensions for the git plugin.
5
+ *
6
+ * The built-in `cwdTracker` (in unbash-walker) handles `git -C DIR
7
+ * subcmd`. Two narrower git flags - `--git-dir=/path` and
8
+ * `--work-tree=/path` - express the same "run this command as if the
9
+ * working directory were /path" intent, but weren't modelled in the
10
+ * walker's core to keep its git knowledge minimal. Parsing them lives
11
+ * in the git plugin as a tracker extension: registered under
12
+ * `trackerExtensions.cwd.git`, the modifier layers on top of the core
13
+ * cwd tracker's own `git` per-command modifier.
14
+ *
15
+ * Scope is `per-command` - consistent with `-C` and git's own runtime
16
+ * behavior: these flags affect only the command they're attached to.
17
+ *
18
+ * Composition with `-C`:
19
+ *
20
+ * `git -C /a --git-dir=/b status`
21
+ *
22
+ * The plugin-merger appends this extension's modifier AFTER the core
23
+ * `-C` modifier under the same basename. The walker then runs BOTH
24
+ * modifiers left-to-right against the same arg list, each layering
25
+ * its own contribution on top of the running recorded value:
26
+ *
27
+ * 1. Core `-C /a` modifier (scans pre-subcommand flags only):
28
+ * running cwd "/initial" -> "/a"
29
+ * 2. This `--git-dir=/b` modifier (scans all args for the two
30
+ * specific flag forms):
31
+ * running cwd "/a" -> "/b"
32
+ *
33
+ * The LATER modifier wins on absolute paths; for relative paths it
34
+ * joins against the running value from the previous step. Test
35
+ * expectation for `git -C /a --git-dir=b status` is `/a/b`.
36
+ *
37
+ * Order in the source matters too:
38
+ *
39
+ * - `git --git-dir=/b -C /a status` -> `/b` (core `-C` runs on
40
+ * the pre-sequential value `/initial`, producing `/a`; then
41
+ * this modifier runs against the same args, overriding to
42
+ * `/b`).
43
+ * - `git -C /a --git-dir=/b status` -> `/b` (same left-to-right
44
+ * modifier order; the core scanner produces `/a`, this one
45
+ * overrides to `/b`).
46
+ *
47
+ * In both cases the `--git-dir=` / `--work-tree=` flags win, matching
48
+ * git's documented precedence.
49
+ *
50
+ * Known limitations:
51
+ *
52
+ * - `--git-dir` without an `=` (space-separated form:
53
+ * `git --git-dir /path ...`) is NOT handled. Both git and shell
54
+ * tokenize this as two words; walker modifiers today only see the
55
+ * single `--git-dir` token. Wiring a two-token form is possible
56
+ * but agents overwhelmingly emit the `=` form; follow-up.
57
+ * - Non-static targets (`--git-dir=$VAR`) return `undefined`,
58
+ * collapsing the per-command cwd to `"unknown"`. Predicates then
59
+ * apply `onUnknown`.
60
+ * - `--work-tree` without `--git-dir` is semantically incomplete in
61
+ * real git (the gitdir still points at the default), but for cwd
62
+ * tracking purposes we treat them identically - they both mean
63
+ * "the command's effective cwd is this path". If a rule author
64
+ * needs the distinction (e.g. a repo-path vs tree-path
65
+ * predicate), they can split it in their own plugin.
66
+ *
67
+ * Known accepted false-positives:
68
+ *
69
+ * - `git log --git-dir=/repo` -> cwd recorded as /repo. Real git
70
+ * treats post-subcommand `--git-dir=` as a pathspec, not a
71
+ * global flag. Over-match rate is low in practice (agents emit
72
+ * `git --git-dir=/x log`, not the inverse), and stopping the
73
+ * scan at the first non-flag token would break composition with
74
+ * the core `-C` modifier which iterates the same args list.
75
+ * Accepted as a Phase-4 corner.
76
+ * - `git diff -- --git-dir=/x` -> cwd recorded as /x. Arguments
77
+ * after `--` are always pathspecs in real git. Same mitigation
78
+ * rationale as above; stopping the scan at `--` would still
79
+ * need to run after the core `-C` scanner has finished, not
80
+ * before, to avoid the composition breakage.
81
+ */
82
+ import * as path from "node:path";
83
+ import { isStaticallyResolvable } from "@cad0p/unbash-walker";
84
+ /** Regex matched against each `git`-command argument token. */
85
+ const GIT_DIR_RE = /^--git-dir=(.+)$/;
86
+ const WORK_TREE_RE = /^--work-tree=(.+)$/;
87
+ /**
88
+ * Read a word's static value. Falls back to `text` when `value`
89
+ * isn't materialised (pure literal fast path in unbash).
90
+ */
91
+ function wordValue(w) {
92
+ return w?.value ?? w?.text;
93
+ }
94
+ /**
95
+ * Scan git's pre-subcommand flags for `--git-dir=DIR` / `--work-tree=DIR`
96
+ * and layer the target on top of the current cwd.
97
+ *
98
+ * Stops at the first non-flag token (the subcommand) - identical shape
99
+ * to the core `-C` scanner. Multiple occurrences compose left-to-right
100
+ * (an absolute path replaces, a relative path joins to the running
101
+ * cwd). If we encounter a target we can't statically resolve, we bail
102
+ * returning `undefined`; the walker translates that to the cwd
103
+ * tracker's `"unknown"` sentinel.
104
+ */
105
+ const gitCwdExtension = {
106
+ scope: "per-command",
107
+ apply: (args, current) => {
108
+ let cwd = current;
109
+ // Scan ALL args (not just pre-subcommand): we match only the two
110
+ // specific flag forms `--git-dir=X` / `--work-tree=X`, so there's
111
+ // no ambiguity with subcommand arguments. `git log
112
+ // --grep="--git-dir=foo"` is safe because the quoted string is a
113
+ // SINGLE argument whose text starts with `--grep`, not
114
+ // `--git-dir`, so the regex doesn't match.
115
+ //
116
+ // A stop-at-non-flag scan (mirroring the core `-C` modifier)
117
+ // would break composition with `-C`: the walker runs this
118
+ // modifier AGAINST THE SAME args the core `-C` modifier saw, not
119
+ // a reduced arg list. On `git -C /a --git-dir=/b status`, the
120
+ // args are still `[-C, /a, --git-dir=/b, status]`; a stop-at-
121
+ // non-flag scan would terminate at `/a` (the value of `-C`,
122
+ // which doesn't start with `-`) and miss the `--git-dir=` flag.
123
+ for (const w of args) {
124
+ const tok = wordValue(w);
125
+ if (tok === undefined)
126
+ continue;
127
+ const gitDirMatch = tok.match(GIT_DIR_RE);
128
+ const workTreeMatch = tok.match(WORK_TREE_RE);
129
+ const match = gitDirMatch ?? workTreeMatch;
130
+ if (!match)
131
+ continue;
132
+ // `--git-dir=$VAR` / `--work-tree=$(...)` - the captured
133
+ // piece is there in the raw text, but the overall Word is
134
+ // non-static. Refuse to invent a value.
135
+ if (!isStaticallyResolvable(w))
136
+ return undefined;
137
+ const target = match[1];
138
+ if (target === undefined || target.length === 0)
139
+ continue;
140
+ cwd = path.isAbsolute(target) ? target : path.join(cwd, target);
141
+ }
142
+ return cwd;
143
+ },
144
+ };
145
+ /**
146
+ * Modifiers the git plugin registers under `cwd.git`. Exposed as an
147
+ * array so the plugin merger sees each entry distinctly; the runtime
148
+ * could today also accept a single modifier, but the array form
149
+ * leaves room for future git-specific cwd tweaks without changing
150
+ * the plugin manifest.
151
+ */
152
+ export const gitCwdExtensions = [
153
+ gitCwdExtension,
154
+ ];
155
+ //# sourceMappingURL=cwd-extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cwd-extensions.js","sourceRoot":"","sources":["../../../src/plugins/git/cwd-extensions.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,uBAAuB;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,sBAAsB,EAAiB,MAAM,sBAAsB,CAAC;AAE7E,+DAA+D;AAC/D,MAAM,UAAU,GAAG,kBAAkB,CAAC;AACtC,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAE1C;;;GAGG;AACH,SAAS,SAAS,CAAC,CAAmB;IACpC,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,eAAe,GAAqB;IACxC,KAAK,EAAE,aAAa;IACpB,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;QACvB,IAAI,GAAG,GAAG,OAAO,CAAC;QAClB,iEAAiE;QACjE,kEAAkE;QAClE,mDAAmD;QACnD,iEAAiE;QACjE,uDAAuD;QACvD,2CAA2C;QAC3C,EAAE;QACF,6DAA6D;QAC7D,0DAA0D;QAC1D,iEAAiE;QACjE,8DAA8D;QAC9D,8DAA8D;QAC9D,4DAA4D;QAC5D,gEAAgE;QAChE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,GAAG,KAAK,SAAS;gBAAE,SAAS;YAEhC,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC1C,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,WAAW,IAAI,aAAa,CAAC;YAC3C,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,yDAAyD;YACzD,0DAA0D;YAC1D,wCAAwC;YACxC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBAAE,OAAO,SAAS,CAAC;YAEjD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAC1D,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAClE,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,eAAoC;CACrC,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Raw git query helpers for the git plugin — reusable functions that
3
+ * shell out to git and return trimmed raw data (or `null` on any
4
+ * failure). Exposed so downstream plugins can compose per-directory
5
+ * scans without re-implementing the shell invocations, and so the
6
+ * predicates in this plugin stay thin pattern-matching wrappers
7
+ * around the helpers.
8
+ *
9
+ * Each helper:
10
+ *
11
+ * - takes `(ctx, cwd?)`; `cwd` defaults to `ctx.cwd`, letting
12
+ * callers iterate over per-subpackage git dirs (common in
13
+ * multi-package workspace contexts like `cr --all`).
14
+ * - returns the raw data on success, `null` on any failure
15
+ * (non-zero exit, spawn error, exception thrown inside
16
+ * `ctx.exec`). Callers layer their own "what does null mean"
17
+ * policy — predicates apply `onUnknown`; custom logic can
18
+ * inspect the null directly.
19
+ * - relies on the evaluator's per-tool_call `exec` memoization
20
+ * (`(cmd, args, cwd)` tuple) so multiple helpers reading the
21
+ * same repo state don't re-fork git.
22
+ *
23
+ * ## Consumer note
24
+ *
25
+ * The helpers return **raw query results** and are INTENTIONALLY free
26
+ * of the `onUnknown` / pattern-matching layer. That layer lives in
27
+ * `predicates.ts`. If you want predicate-style semantics, call the
28
+ * matching `when.*` handler (also re-exported from the plugin index)
29
+ * rather than the helper.
30
+ *
31
+ * ## Branch caveat
32
+ *
33
+ * {@link getBranch} intentionally DOES NOT consult `walkerState.branch`
34
+ * — the walker-state short-circuit is a predicate-layer concern (see
35
+ * the `branch` predicate's JSDoc for the three-way tracker
36
+ * discrimination). Downstream plugins iterating per-directory usually
37
+ * care about the on-disk branch at each directory, not the
38
+ * walker-tracked branch of the current bash chain at `ctx.cwd`; the
39
+ * helper therefore always shells out.
40
+ */
41
+ import type { PredicateContext } from "../../schema.ts";
42
+ /**
43
+ * `git branch --show-current` at `cwd` (default: `ctx.cwd`). Returns
44
+ * the current branch name, or `null` when the command fails OR stdout
45
+ * is empty (detached HEAD).
46
+ *
47
+ * Does NOT consult `ctx.walkerState.branch` — see file header
48
+ * "Branch caveat" for why. Predicates that need the walker-state
49
+ * short-circuit should use the `branch` predicate handler instead.
50
+ */
51
+ export declare function getBranch(ctx: PredicateContext, cwd?: string): Promise<string | null>;
52
+ /**
53
+ * `git rev-parse --abbrev-ref @{upstream}` at `cwd` (default:
54
+ * `ctx.cwd`). Returns the tracking branch name (e.g. `origin/main`),
55
+ * or `null` when no upstream is configured or the command fails.
56
+ */
57
+ export declare function getUpstream(ctx: PredicateContext, cwd?: string): Promise<string | null>;
58
+ /**
59
+ * `git rev-list --count <wrt>..HEAD` at `cwd` (default: `ctx.cwd`).
60
+ * Returns the number of commits HEAD is ahead of `wrt`, or `null`
61
+ * when the command fails (e.g. `wrt` doesn't resolve, detached HEAD,
62
+ * not a repo).
63
+ *
64
+ * `wrt` defaults to `@{upstream}`. Pass a specific ref like
65
+ * `"origin/main"` when the upstream configuration isn't guaranteed.
66
+ */
67
+ export declare function getCommitsAhead(ctx: PredicateContext, wrt?: string, cwd?: string): Promise<number | null>;
68
+ /**
69
+ * `git diff --cached --quiet` at `cwd` (default: `ctx.cwd`). Exit
70
+ * code discrimination:
71
+ *
72
+ * - `0` → no staged changes → returns `false`
73
+ * - `1` → staged changes exist → returns `true`
74
+ * - anything else (spawn error, weird exit) → returns `null`
75
+ */
76
+ export declare function getStagedChanges(ctx: PredicateContext, cwd?: string): Promise<boolean | null>;
77
+ /**
78
+ * `git status --porcelain` at `cwd` (default: `ctx.cwd`). Returns
79
+ * `true` when the working tree is clean (empty output), `false`
80
+ * when dirty, `null` on any command failure.
81
+ */
82
+ export declare function getWorkingTreeClean(ctx: PredicateContext, cwd?: string): Promise<boolean | null>;
83
+ /**
84
+ * `git config --get remote.origin.url` at `cwd` (default: `ctx.cwd`).
85
+ * Returns the origin URL string, or `null` when no origin is
86
+ * configured or the command fails.
87
+ */
88
+ export declare function getRemoteUrl(ctx: PredicateContext, cwd?: string): Promise<string | null>;
89
+ //# sourceMappingURL=git-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.d.ts","sourceRoot":"","sources":["../../../src/plugins/git/git-ops.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAuBxD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,gBAAgB,EACrB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,gBAAgB,EACrB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,GAAG,GAAE,MAAsB,EAC3B,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKxB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,gBAAgB,EACrB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAUzB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,gBAAgB,EACrB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAIzB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,gBAAgB,EACrB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB"}
@@ -0,0 +1,107 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Run a git command in the given cwd and return its trimmed stdout on
5
+ * exit 0, or `null` on any failure. Thin wrapper used by every helper
6
+ * here so failure modes (non-zero exit, spawn error, exception)
7
+ * collapse to a uniform `null`.
8
+ */
9
+ async function tryGit(ctx, args, cwd) {
10
+ try {
11
+ const opts = cwd !== undefined ? { cwd } : { cwd: ctx.cwd };
12
+ const res = await ctx.exec("git", [...args], opts);
13
+ if (res.exitCode !== 0)
14
+ return null;
15
+ return res.stdout.trim();
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * `git branch --show-current` at `cwd` (default: `ctx.cwd`). Returns
23
+ * the current branch name, or `null` when the command fails OR stdout
24
+ * is empty (detached HEAD).
25
+ *
26
+ * Does NOT consult `ctx.walkerState.branch` — see file header
27
+ * "Branch caveat" for why. Predicates that need the walker-state
28
+ * short-circuit should use the `branch` predicate handler instead.
29
+ */
30
+ export async function getBranch(ctx, cwd) {
31
+ const out = await tryGit(ctx, ["branch", "--show-current"], cwd);
32
+ if (out === null || out.length === 0)
33
+ return null;
34
+ return out;
35
+ }
36
+ /**
37
+ * `git rev-parse --abbrev-ref @{upstream}` at `cwd` (default:
38
+ * `ctx.cwd`). Returns the tracking branch name (e.g. `origin/main`),
39
+ * or `null` when no upstream is configured or the command fails.
40
+ */
41
+ export async function getUpstream(ctx, cwd) {
42
+ const out = await tryGit(ctx, ["rev-parse", "--abbrev-ref", "@{upstream}"], cwd);
43
+ if (out === null || out.length === 0)
44
+ return null;
45
+ return out;
46
+ }
47
+ /**
48
+ * `git rev-list --count <wrt>..HEAD` at `cwd` (default: `ctx.cwd`).
49
+ * Returns the number of commits HEAD is ahead of `wrt`, or `null`
50
+ * when the command fails (e.g. `wrt` doesn't resolve, detached HEAD,
51
+ * not a repo).
52
+ *
53
+ * `wrt` defaults to `@{upstream}`. Pass a specific ref like
54
+ * `"origin/main"` when the upstream configuration isn't guaranteed.
55
+ */
56
+ export async function getCommitsAhead(ctx, wrt = "@{upstream}", cwd) {
57
+ const out = await tryGit(ctx, ["rev-list", "--count", `${wrt}..HEAD`], cwd);
58
+ if (out === null)
59
+ return null;
60
+ const count = Number.parseInt(out, 10);
61
+ return Number.isFinite(count) ? count : null;
62
+ }
63
+ /**
64
+ * `git diff --cached --quiet` at `cwd` (default: `ctx.cwd`). Exit
65
+ * code discrimination:
66
+ *
67
+ * - `0` → no staged changes → returns `false`
68
+ * - `1` → staged changes exist → returns `true`
69
+ * - anything else (spawn error, weird exit) → returns `null`
70
+ */
71
+ export async function getStagedChanges(ctx, cwd) {
72
+ try {
73
+ const opts = cwd !== undefined ? { cwd } : { cwd: ctx.cwd };
74
+ const res = await ctx.exec("git", ["diff", "--cached", "--quiet"], opts);
75
+ if (res.exitCode === 0)
76
+ return false;
77
+ if (res.exitCode === 1)
78
+ return true;
79
+ return null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * `git status --porcelain` at `cwd` (default: `ctx.cwd`). Returns
87
+ * `true` when the working tree is clean (empty output), `false`
88
+ * when dirty, `null` on any command failure.
89
+ */
90
+ export async function getWorkingTreeClean(ctx, cwd) {
91
+ const out = await tryGit(ctx, ["status", "--porcelain"], cwd);
92
+ if (out === null)
93
+ return null;
94
+ return out.length === 0;
95
+ }
96
+ /**
97
+ * `git config --get remote.origin.url` at `cwd` (default: `ctx.cwd`).
98
+ * Returns the origin URL string, or `null` when no origin is
99
+ * configured or the command fails.
100
+ */
101
+ export async function getRemoteUrl(ctx, cwd) {
102
+ const out = await tryGit(ctx, ["config", "--get", "remote.origin.url"], cwd);
103
+ if (out === null || out.length === 0)
104
+ return null;
105
+ return out;
106
+ }
107
+ //# sourceMappingURL=git-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.js","sourceRoot":"","sources":["../../../src/plugins/git/git-ops.ts"],"names":[],"mappings":"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"}