@hassangameryt/freeflow 0.8.0 → 0.9.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 (290) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ // What a commit would hold, and whose it is, before touching the index: every staged, unstaged and untracked path,
3
+ // marked yours, pre-existing or unknown; staged changes that were already staged before your work (never absorb or
4
+ // unstage them); files with both staged and unstaged changes; sensitive-looking paths and Working Records; and what
5
+ // `git diff --cached --check` finds. Read-only apart from its own snapshot file.
6
+ // Usage: commit-state.mjs snapshot [--root <repository>] [--scratch <dir>]
7
+ // commit-state.mjs [--mine <path>]... [--root <repository>] [--scratch <dir>] [--json]
8
+ // `snapshot`, taken before your first edit, records the dirty and staged state that is not yours. Without one, --mine
9
+ // names the paths you changed; a path neither names is "unknown". --scratch defaults to a folder under the system
10
+ // temporary directory. Exit code 0 when it reports, 2 on a usage error or outside a Git repository.
11
+ import { spawnSync } from "node:child_process";
12
+ import { createHash } from "node:crypto";
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { basename, join, relative, resolve } from "node:path";
16
+ import { parseArgs } from "node:util";
17
+ import { git, repositoryRoot, workingState } from "./lib/git-state.mjs";
18
+
19
+ const USAGE = [
20
+ "Usage: commit-state.mjs snapshot [--root <repository>] [--scratch <dir>]",
21
+ " commit-state.mjs [--mine <path>]... [--root <repository>] [--scratch <dir>] [--json]",
22
+ ].join("\n");
23
+ let parsed;
24
+ try {
25
+ parsed = parseArgs({
26
+ allowPositionals: true,
27
+ options: {
28
+ root: { type: "string" },
29
+ scratch: { type: "string" },
30
+ mine: { type: "string", multiple: true, default: [] },
31
+ json: { type: "boolean", default: false },
32
+ },
33
+ });
34
+ } catch (error) {
35
+ console.error(`${error.message}\n${USAGE}`);
36
+ process.exit(2);
37
+ }
38
+ const { values, positionals } = parsed;
39
+ if (positionals.length > 1 || (positionals.length && positionals[0] !== "snapshot")) {
40
+ console.error(USAGE);
41
+ process.exit(2);
42
+ }
43
+ const root = repositoryRoot(values.root ?? ".");
44
+ if (!root) {
45
+ console.error("not inside a Git repository");
46
+ process.exit(2);
47
+ }
48
+ const scratch = resolve(
49
+ values.scratch ??
50
+ join(
51
+ tmpdir(),
52
+ "freeflow-commit-state",
53
+ `${basename(root)}-${createHash("sha256").update(root).digest("hex").slice(0, 8)}`,
54
+ ),
55
+ );
56
+ const snapshotPath = join(scratch, "commit-state-snapshot.json");
57
+
58
+ // A path's identity in the index and in the working tree, so a later change to either shows.
59
+ const identity = (path) => ({
60
+ index: git(root, ["ls-files", "-s", "--", path])?.split(/\s+/)[1] ?? null,
61
+ tree: existsSync(join(root, path)) ? git(root, ["hash-object", "--", path]) : null,
62
+ });
63
+ const working = workingState(root);
64
+
65
+ if (positionals[0] === "snapshot") {
66
+ mkdirSync(scratch, { recursive: true });
67
+ const paths = Object.fromEntries(working.entries.map((entry) => [entry.path, { ...entry, ...identity(entry.path) }]));
68
+ writeFileSync(
69
+ snapshotPath,
70
+ `${JSON.stringify({ root, head: git(root, ["rev-parse", "HEAD"]), at: new Date().toISOString(), paths }, null, 2)}\n`,
71
+ );
72
+ console.log(`Snapshot of ${working.entries.length} pre-existing changed path(s): ${snapshotPath}`);
73
+ process.exit(0);
74
+ }
75
+
76
+ const snapshot = existsSync(snapshotPath) ? JSON.parse(readFileSync(snapshotPath, "utf8")) : null;
77
+ const mine = new Set(values.mine.map((path) => relative(root, resolve(path)).split("\\").join("/")));
78
+ function owner(entry) {
79
+ if (snapshot) {
80
+ const before = snapshot.paths[entry.path];
81
+ if (!before) return "yours";
82
+ const now = identity(entry.path);
83
+ return now.index === before.index && now.tree === before.tree ? "pre-existing" : "mixed: pre-existing plus yours";
84
+ }
85
+ // A --mine path names a file, or a folder holding it.
86
+ if ([...mine].some((path) => entry.path === path || entry.path.startsWith(`${path}/`))) return "yours";
87
+ return "unknown";
88
+ }
89
+ const SENSITIVE =
90
+ /(^|\/)(\.env(\..*)?|\.npmrc|\.pypirc|id_(rsa|ed25519|ecdsa)(\.pub)?|.*\.(pem|key|p12|pfx|keystore)|credentials?(\.json)?|secrets?(\.[a-z]+)?|.*\.tfstate)$/i;
91
+ const flags = (entry) => [
92
+ ...(/^\.freeflow\/(tasks\/.*)?$/.test(entry.path) ? ["Working Record: never stage"] : []),
93
+ ...(SENSITIVE.test(entry.path) ? ["sensitive-looking: inspect before staging"] : []),
94
+ ];
95
+ const describe = (entry) => ({
96
+ path: entry.path,
97
+ ...(entry.from ? { from: entry.from } : {}),
98
+ index: entry.x,
99
+ tree: entry.y,
100
+ owner: owner(entry),
101
+ partial: entry.x !== " " && entry.x !== "?" && entry.y !== " " && entry.y !== "?",
102
+ flags: flags(entry),
103
+ });
104
+ const staged = working.staged.map(describe);
105
+ const unstaged = working.unstaged.filter((entry) => entry.x === " ").map(describe);
106
+ const untracked = working.untracked.map(describe);
107
+ const conflicted = working.conflicted.map(describe);
108
+ // Conflict markers and whitespace damage in what is staged; Git exits nonzero when it finds any.
109
+ const cached = spawnSync("git", ["diff", "--cached", "--check"], { cwd: root, encoding: "utf8" });
110
+ const check =
111
+ cached.status === 0 ? "clean" : (cached.stdout || cached.stderr).trim().split("\n").slice(0, 20).join("\n");
112
+ const report = {
113
+ command: "commit-state",
114
+ root,
115
+ ownership: snapshot ? `snapshot of ${snapshot.at}` : mine.size ? "--mine paths" : "unknown: no snapshot or --mine",
116
+ staged,
117
+ stagedNotYours: staged.filter((each) => each.owner !== "yours").map((each) => each.path),
118
+ unstaged,
119
+ untracked,
120
+ conflicted,
121
+ cachedCheck: check,
122
+ };
123
+
124
+ if (values.json) console.log(JSON.stringify(report, null, 2));
125
+ else {
126
+ const line = (each) =>
127
+ ` ${each.index}${each.tree} ${each.from ? `${each.from} -> ` : ""}${each.path} [${each.owner}]${each.partial ? " (staged and unstaged changes)" : ""}${each.flags.length ? ` ! ${each.flags.join("; ")}` : ""}`;
128
+ const out = [`Ownership from: ${report.ownership}`];
129
+ out.push(`Staged (${staged.length}):`, ...(staged.length ? staged.map(line) : [" none"]));
130
+ if (report.stagedNotYours.length)
131
+ out.push(
132
+ ` Not yours, or not known to be: ${report.stagedNotYours.join(", ")}. Do not absorb or unstage them; ask which commit the user wants.`,
133
+ );
134
+ out.push(`Unstaged only (${unstaged.length}):`, ...(unstaged.length ? unstaged.map(line) : [" none"]));
135
+ out.push(`Untracked (${untracked.length}):`, ...(untracked.length ? untracked.map(line) : [" none"]));
136
+ if (conflicted.length) out.push(`Conflicted (${conflicted.length}):`, ...conflicted.map(line));
137
+ out.push(`git diff --cached --check: ${check}`);
138
+ console.log(out.join("\n"));
139
+ }
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // The delivery state in one read-only call, before any commit, push, merge, cleanup or release: branch or detached
3
+ // HEAD, upstream and ahead/behind from local refs, staged, unstaged, untracked and conflicted counts, a Git operation
4
+ // left in progress, every worktree with the one holding this session's working directory flagged, the latest tag, the
5
+ // declared version and the changelog's Unreleased section. Anything that needs the network is reported unavailable.
6
+ // Usage: delivery-state.mjs [--root <repository>] [--cwd <session directory>] [--json]
7
+ // --cwd defaults to the current directory. Exit code 0 when it reports, 2 outside a Git repository.
8
+ import { parseArgs } from "node:util";
9
+ import {
10
+ branchState,
11
+ changelog,
12
+ inProgress,
13
+ latestTag,
14
+ projectVersion,
15
+ repositoryRoot,
16
+ workingState,
17
+ worktrees,
18
+ } from "./lib/git-state.mjs";
19
+
20
+ const USAGE = "Usage: delivery-state.mjs [--root <repository>] [--cwd <session directory>] [--json]";
21
+ let values;
22
+ try {
23
+ ({ values } = parseArgs({
24
+ options: { root: { type: "string" }, cwd: { type: "string" }, json: { type: "boolean", default: false } },
25
+ }));
26
+ } catch (error) {
27
+ console.error(`${error.message}\n${USAGE}`);
28
+ process.exit(2);
29
+ }
30
+ const cwd = values.cwd ?? process.cwd();
31
+ const root = repositoryRoot(values.root ?? cwd);
32
+ if (!root) {
33
+ console.error("not inside a Git repository");
34
+ process.exit(2);
35
+ }
36
+
37
+ const branch = branchState(root);
38
+ const working = workingState(root);
39
+ const operations = inProgress(root);
40
+ const trees = worktrees(root, cwd);
41
+ const tag = latestTag(root);
42
+ const version = projectVersion(root);
43
+ const notes = changelog(root);
44
+ const state = {
45
+ command: "delivery-state",
46
+ root,
47
+ branch,
48
+ working: {
49
+ staged: working.staged.length,
50
+ unstaged: working.unstaged.length,
51
+ untracked: working.untracked.length,
52
+ conflicted: working.conflicted.length,
53
+ },
54
+ inProgress: operations,
55
+ worktrees: trees,
56
+ latestTag: tag,
57
+ version,
58
+ versionTagged:
59
+ version?.version && tag ? [tag.tag, `v${version.version}`].includes(tag.tag) || tag.tag === version.version : false,
60
+ changelog: notes,
61
+ unavailable: [
62
+ "the remote's current state: ahead/behind uses local remote-tracking refs (fetch to refresh them, when that is covered)",
63
+ "pull requests, CI results, registry versions and deployments",
64
+ ],
65
+ };
66
+
67
+ if (values.json) console.log(JSON.stringify(state, null, 2));
68
+ else {
69
+ const out = [];
70
+ out.push(
71
+ branch.branch
72
+ ? `Branch: ${branch.branch}${branch.upstream ? `, upstream ${branch.upstream} (ahead ${branch.ahead}, behind ${branch.behind}, from local refs)` : ", no upstream"}`
73
+ : branch.detached
74
+ ? `Detached HEAD at ${branch.head.slice(0, 12)}`
75
+ : "No commits yet",
76
+ );
77
+ if (branch.head) out.push(`HEAD: ${branch.head.slice(0, 12)} ${branch.subject}`);
78
+ out.push(
79
+ `Working tree: ${state.working.staged} staged, ${state.working.unstaged} unstaged, ${state.working.untracked} untracked, ${state.working.conflicted} conflicted`,
80
+ );
81
+ out.push(`In progress: ${operations.length ? operations.join(", ") : "no Git operation"}`);
82
+ out.push(`Worktrees (${trees.length}):`);
83
+ for (const tree of trees)
84
+ out.push(
85
+ ` ${tree.path} ${tree.branch ?? (tree.detached ? "(detached)" : "")} ${tree.head ?? ""}${tree.sessionCwd ? " <- this session's cwd is inside" : ""}${tree.locked ? " [locked]" : ""}${tree.prunable ? " [prunable]" : ""}`,
86
+ );
87
+ out.push(
88
+ `Latest tag: ${tag ? `${tag.tag} at ${tag.commit}, ${tag.commitsSince} commit(s) since` : "none"}; version: ${version ? `${version.version ?? "unreadable"} (${version.source})` : "none declared"}`,
89
+ );
90
+ if (notes)
91
+ out.push(
92
+ `Changelog ${notes.file}: ${
93
+ notes.unreleased
94
+ ? `Unreleased has ${notes.unreleased.entries} entr${notes.unreleased.entries === 1 ? "y" : "ies"}${
95
+ Object.keys(notes.unreleased.categories).length
96
+ ? ` (${Object.entries(notes.unreleased.categories)
97
+ .map(([name, count]) => `${name} ${count}`)
98
+ .join(", ")})`
99
+ : ""
100
+ }`
101
+ : "no Unreleased section"
102
+ }`,
103
+ );
104
+ else out.push("Changelog: none found");
105
+ out.push(`Unavailable without the network: ${state.unavailable.join("; ")}.`);
106
+ console.log(out.join("\n"));
107
+ }
@@ -0,0 +1,178 @@
1
+ // Read-only Git and project facts shared by Deliver Work's scripts. Nothing here fetches, writes or changes state:
2
+ // remote facts come from local remote-tracking refs, which are only as fresh as the last fetch.
3
+ import { execFileSync } from "node:child_process";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { join, relative, resolve, sep } from "node:path";
6
+
7
+ export function run(cwd, command, args) {
8
+ try {
9
+ return execFileSync(command, args, {
10
+ cwd,
11
+ encoding: "utf8",
12
+ maxBuffer: 256 * 1024 * 1024,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ }).trimEnd();
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ export const git = (cwd, args) => run(cwd, "git", args);
21
+
22
+ export function repositoryRoot(start) {
23
+ const root = git(resolve(start), ["rev-parse", "--show-toplevel"]);
24
+ return root ? resolve(root) : null;
25
+ }
26
+
27
+ export function branchState(root) {
28
+ const branch = git(root, ["branch", "--show-current"]) || null;
29
+ const head = git(root, ["rev-parse", "--verify", "--quiet", "HEAD"]);
30
+ const subject = head ? git(root, ["log", "-1", "--format=%s"]) : null;
31
+ const upstream = branch ? git(root, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]) : null;
32
+ let ahead = null;
33
+ let behind = null;
34
+ if (upstream) {
35
+ const counts = git(root, ["rev-list", "--left-right", "--count", `${upstream}...HEAD`]);
36
+ if (counts) [behind, ahead] = counts.split(/\s+/).map(Number);
37
+ }
38
+ return { branch, detached: !branch && Boolean(head), head, subject, upstream, ahead, behind };
39
+ }
40
+
41
+ // Porcelain v1 status: staged and unstaged paths, untracked files and conflicts.
42
+ export function workingState(root) {
43
+ const lines = (git(root, ["-c", "core.quotePath=false", "status", "--porcelain=v1", "-z"]) ?? "")
44
+ .split("\0")
45
+ .filter(Boolean);
46
+ const entries = [];
47
+ for (let index = 0; index < lines.length; index += 1) {
48
+ const line = lines[index];
49
+ const x = line[0];
50
+ const y = line[1];
51
+ const path = line.slice(3);
52
+ const entry = { x, y, path };
53
+ if (x === "R" || x === "C") entry.from = lines[(index += 1)];
54
+ entries.push(entry);
55
+ }
56
+ const conflict = (entry) =>
57
+ entry.x === "U" || entry.y === "U" || (entry.x === "A" && entry.y === "A") || (entry.x === "D" && entry.y === "D");
58
+ return {
59
+ entries,
60
+ staged: entries.filter((entry) => entry.x !== " " && entry.x !== "?" && !conflict(entry)),
61
+ unstaged: entries.filter((entry) => entry.y !== " " && entry.y !== "?" && !conflict(entry)),
62
+ untracked: entries.filter((entry) => entry.x === "?"),
63
+ conflicted: entries.filter(conflict),
64
+ };
65
+ }
66
+
67
+ // A merge, rebase, cherry-pick, revert, bisect or patch application left in progress.
68
+ export function inProgress(root) {
69
+ const gitDir = git(root, ["rev-parse", "--absolute-git-dir"]);
70
+ if (!gitDir) return [];
71
+ const markers = [
72
+ ["MERGE_HEAD", "merge"],
73
+ ["rebase-merge", "rebase"],
74
+ ["rebase-apply/applying", "am (patch application)"],
75
+ ["rebase-apply", "rebase"],
76
+ ["CHERRY_PICK_HEAD", "cherry-pick"],
77
+ ["REVERT_HEAD", "revert"],
78
+ ["BISECT_LOG", "bisect"],
79
+ ];
80
+ const found = [];
81
+ for (const [marker, operation] of markers)
82
+ if (existsSync(join(gitDir, marker)) && !found.includes(operation)) found.push(operation);
83
+ if (found.includes("am (patch application)")) return found.filter((each) => each !== "rebase");
84
+ return found;
85
+ }
86
+
87
+ // Worktrees, flagging the one holding the given directory (the session's working directory).
88
+ export function worktrees(root, cwd) {
89
+ const text = git(root, ["worktree", "list", "--porcelain"]) ?? "";
90
+ const here = resolve(cwd);
91
+ return text
92
+ .split("\n\n")
93
+ .filter(Boolean)
94
+ .map((block) => {
95
+ const fields = Object.fromEntries(
96
+ block.split("\n").map((line) => {
97
+ const space = line.indexOf(" ");
98
+ return space === -1 ? [line, true] : [line.slice(0, space), line.slice(space + 1)];
99
+ }),
100
+ );
101
+ const path = resolve(fields.worktree);
102
+ const inside = here === path || here.startsWith(`${path}${sep}`);
103
+ return {
104
+ path,
105
+ branch: typeof fields.branch === "string" ? fields.branch.replace(/^refs\/heads\//, "") : null,
106
+ head: typeof fields.HEAD === "string" ? fields.HEAD.slice(0, 12) : null,
107
+ detached: fields.detached === true,
108
+ locked: fields.locked !== undefined,
109
+ prunable: fields.prunable !== undefined,
110
+ sessionCwd: inside,
111
+ };
112
+ })
113
+ .map((tree, _, all) => {
114
+ // The innermost worktree holding the cwd is the session's; an outer one only contains it on disk.
115
+ const holders = all.filter((each) => each.sessionCwd);
116
+ const innermost = holders.sort((a, b) => b.path.length - a.path.length)[0];
117
+ return { ...tree, sessionCwd: innermost ? innermost.path === tree.path : false };
118
+ });
119
+ }
120
+
121
+ export function latestTag(root) {
122
+ const tag = git(root, ["describe", "--tags", "--abbrev=0"]);
123
+ if (!tag) return null;
124
+ const at = git(root, ["rev-list", "-n", "1", tag]);
125
+ const since = git(root, ["rev-list", "--count", `${tag}..HEAD`]);
126
+ return { tag, commit: at?.slice(0, 12) ?? null, commitsSince: since === null ? null : Number(since) };
127
+ }
128
+
129
+ // The project's declared version: package.json, then pyproject.toml, then Cargo.toml.
130
+ export function projectVersion(root) {
131
+ const packagePath = join(root, "package.json");
132
+ if (existsSync(packagePath)) {
133
+ try {
134
+ const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
135
+ if (parsed.version) return { version: parsed.version, source: "package.json", name: parsed.name ?? null };
136
+ } catch {
137
+ return { version: null, source: "package.json (not valid JSON)", name: null };
138
+ }
139
+ }
140
+ for (const file of ["pyproject.toml", "Cargo.toml"]) {
141
+ const path = join(root, file);
142
+ if (!existsSync(path)) continue;
143
+ const match = /^\s*version\s*=\s*["']([^"']+)["']/m.exec(readFileSync(path, "utf8"));
144
+ if (match) return { version: match[1], source: file, name: null };
145
+ }
146
+ return null;
147
+ }
148
+
149
+ // The changelog's Unreleased section: its categories and how many entries each holds.
150
+ export function changelog(root) {
151
+ const file = ["CHANGELOG.md", "CHANGES.md", "HISTORY.md"].find((name) => existsSync(join(root, name)));
152
+ if (!file) return null;
153
+ const lines = readFileSync(join(root, file), "utf8").split("\n");
154
+ const start = lines.findIndex((line) => /^##\s+\[?unreleased\]?/i.test(line));
155
+ const versions = lines
156
+ .filter((line) => /^##\s+\[?v?\d+\.\d+/.test(line))
157
+ .map((line) => /^##\s+\[?v?(\d+\.\d+[^\]\s]*)/.exec(line)[1]);
158
+ if (start === -1) return { file, unreleased: null, versions };
159
+ const end = lines.findIndex((line, index) => index > start && /^##\s/.test(line));
160
+ const section = lines.slice(start + 1, end === -1 ? undefined : end);
161
+ const categories = {};
162
+ let category = "(none)";
163
+ let entries = 0;
164
+ for (const line of section) {
165
+ const heading = /^###\s+(.*)$/.exec(line);
166
+ if (heading) category = heading[1].trim();
167
+ else if (/^\s*[-*]\s+\S/.test(line) && !/^\s{2,}/.test(line)) {
168
+ categories[category] = (categories[category] ?? 0) + 1;
169
+ entries += 1;
170
+ }
171
+ }
172
+ return { file, unreleased: { entries, categories }, versions };
173
+ }
174
+
175
+ export const show = (root, path) => {
176
+ const relativePath = relative(root, path);
177
+ return relativePath.startsWith("..") ? path : relativePath || ".";
178
+ };
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ // The route for a simple push (references/branch-closeout.md, Simple Push): the current branch, its upstream or the remote it would go to, the
3
+ // commits it would send from local refs, and the stop conditions the local state shows. It prints the push command it
4
+ // would use and never runs it.
5
+ // Usage: push-route.mjs [--remote <name>] [--root <repository>] [--json]
6
+ // Exit code 0 when a simple push is possible from what it can see, 1 when a stop condition holds, 2 on a usage error
7
+ // or outside a Git repository.
8
+ import { parseArgs } from "node:util";
9
+ import { branchState, git, inProgress, repositoryRoot, workingState } from "./lib/git-state.mjs";
10
+
11
+ const USAGE = "Usage: push-route.mjs [--remote <name>] [--root <repository>] [--json]";
12
+ let values;
13
+ try {
14
+ ({ values } = parseArgs({
15
+ options: { remote: { type: "string" }, root: { type: "string" }, json: { type: "boolean", default: false } },
16
+ }));
17
+ } catch (error) {
18
+ console.error(`${error.message}\n${USAGE}`);
19
+ process.exit(2);
20
+ }
21
+ const root = repositoryRoot(values.root ?? ".");
22
+ if (!root) {
23
+ console.error("not inside a Git repository");
24
+ process.exit(2);
25
+ }
26
+
27
+ const branch = branchState(root);
28
+ const remotes = (git(root, ["remote"]) ?? "").split("\n").filter(Boolean);
29
+ const urls = Object.fromEntries(remotes.map((name) => [name, git(root, ["remote", "get-url", "--push", name])]));
30
+ const stops = [];
31
+ const notes = [];
32
+ let remote = values.remote ?? null;
33
+ let destination = null;
34
+ let command = null;
35
+
36
+ if (!branch.head) stops.push("no commits to push");
37
+ if (branch.detached) stops.push("HEAD is detached: push a named branch");
38
+ for (const operation of inProgress(root)) stops.push(`a ${operation} is in progress`);
39
+ if (values.remote && !remotes.includes(values.remote)) stops.push(`no remote named ${values.remote}`);
40
+
41
+ if (branch.branch && branch.upstream) {
42
+ const [upstreamRemote, ...rest] = branch.upstream.split("/");
43
+ destination = { remote: upstreamRemote, branch: rest.join("/") };
44
+ if (remote && remote !== upstreamRemote)
45
+ stops.push(`the upstream is on ${upstreamRemote}, not ${remote}: pushing elsewhere changes the route`);
46
+ remote = upstreamRemote;
47
+ if (destination.branch !== branch.branch)
48
+ notes.push(`the upstream branch is named ${destination.branch}, not ${branch.branch}`);
49
+ if (branch.behind && branch.ahead)
50
+ stops.push(`local and ${branch.upstream} have diverged (ahead ${branch.ahead}, behind ${branch.behind})`);
51
+ else if (branch.behind) stops.push(`${branch.upstream} has ${branch.behind} commit(s) this branch lacks`);
52
+ else if (!branch.ahead) notes.push(`nothing to push: ${branch.upstream} already has HEAD (local refs)`);
53
+ command = `git push ${remote} ${branch.branch}${destination.branch !== branch.branch ? `:${destination.branch}` : ""}`;
54
+ } else if (branch.branch) {
55
+ if (!remote) {
56
+ if (remotes.length === 1) [remote] = remotes;
57
+ else if (remotes.includes("origin")) {
58
+ remote = "origin";
59
+ notes.push(`several remotes (${remotes.join(", ")}); origin assumed: confirm it`);
60
+ } else
61
+ stops.push(remotes.length ? `no upstream and several remotes (${remotes.join(", ")}): name one` : "no remote");
62
+ }
63
+ if (remote) {
64
+ destination = { remote, branch: branch.branch };
65
+ const existing = git(root, ["rev-parse", "--verify", "--quiet", `refs/remotes/${remote}/${branch.branch}`]);
66
+ if (existing) {
67
+ const behind = Number(git(root, ["rev-list", "--count", `HEAD..${remote}/${branch.branch}`]) ?? 0);
68
+ if (behind) stops.push(`${remote}/${branch.branch} exists with ${behind} commit(s) this branch lacks`);
69
+ notes.push(`${remote}/${branch.branch} exists but is not the upstream: setting it is part of the yes`);
70
+ }
71
+ command = `git push --set-upstream ${remote} ${branch.branch}`;
72
+ notes.push("no upstream yet: this push sets one");
73
+ }
74
+ }
75
+
76
+ const commits = destination
77
+ ? (
78
+ git(root, ["log", "--oneline", "--no-decorate", `${destination.remote}/${destination.branch}..HEAD`]) ??
79
+ git(root, ["log", "--oneline", "--no-decorate", "HEAD", "--not", `--remotes=${destination.remote}`]) ??
80
+ ""
81
+ )
82
+ .split("\n")
83
+ .filter(Boolean)
84
+ : [];
85
+ const working = workingState(root);
86
+ if (working.staged.length || working.unstaged.length)
87
+ notes.push(`${working.staged.length + working.unstaged.length} uncommitted change(s) will not be pushed`);
88
+
89
+ const report = {
90
+ command: "push-route",
91
+ root,
92
+ branch: branch.branch,
93
+ head: branch.head?.slice(0, 12) ?? null,
94
+ upstream: branch.upstream,
95
+ ahead: branch.ahead,
96
+ behind: branch.behind,
97
+ remotes: urls,
98
+ destination,
99
+ commits: commits.slice(0, 20),
100
+ commitsCut: Math.max(0, commits.length - 20),
101
+ pushCommand: stops.length ? null : command,
102
+ stops,
103
+ notes,
104
+ notChecked: [
105
+ "the remote's current state (counts use local refs)",
106
+ "branch protection and shared-branch policy",
107
+ "whether a force push would be needed",
108
+ ],
109
+ };
110
+ if (values.json) console.log(JSON.stringify(report, null, 2));
111
+ else {
112
+ console.log(
113
+ `Branch ${branch.branch ?? "(detached)"} at ${report.head ?? "none"} -> ${destination ? `${destination.remote}/${destination.branch}` : "no destination"}${remote && urls[remote] ? ` (${urls[remote]})` : ""}`,
114
+ );
115
+ console.log(`Commits to send (${commits.length}, from local refs):`);
116
+ for (const commit of report.commits) console.log(` ${commit}`);
117
+ if (report.commitsCut) console.log(` ... ${report.commitsCut} more`);
118
+ for (const note of notes) console.log(`Note: ${note}`);
119
+ console.log(stops.length ? `Stop:\n${stops.map((each) => `- ${each}`).join("\n")}` : `Simple push: ${command}`);
120
+ console.log(`Not checked: ${report.notChecked.join("; ")}.`);
121
+ }
122
+ process.exit(stops.length ? 1 : 0);