@aipper/aiws 0.0.28 → 0.0.30

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 (267) hide show
  1. package/README.md +18 -2
  2. package/bin/aiws.js +2 -2
  3. package/dist/aiws-package.d.ts +4 -0
  4. package/dist/aiws-package.js +11 -0
  5. package/dist/aiws-package.js.map +1 -0
  6. package/dist/aiws-paths.d.ts +48 -0
  7. package/dist/aiws-paths.js +129 -0
  8. package/dist/aiws-paths.js.map +1 -0
  9. package/dist/backup.d.ts +27 -0
  10. package/dist/backup.js +125 -0
  11. package/dist/backup.js.map +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +773 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/codex-prompts.d.ts +17 -0
  16. package/dist/codex-prompts.js +49 -0
  17. package/dist/codex-prompts.js.map +1 -0
  18. package/dist/codex-skills.d.ts +17 -0
  19. package/dist/codex-skills.js +75 -0
  20. package/dist/codex-skills.js.map +1 -0
  21. package/dist/commands/change-advice.d.ts +25 -0
  22. package/dist/commands/change-advice.js +197 -0
  23. package/dist/commands/change-advice.js.map +1 -0
  24. package/dist/commands/change-evidence-command.d.ts +29 -0
  25. package/dist/commands/change-evidence-command.js +213 -0
  26. package/dist/commands/change-evidence-command.js.map +1 -0
  27. package/dist/commands/change-evidence-entry.d.ts +16 -0
  28. package/dist/commands/change-evidence-entry.js +35 -0
  29. package/dist/commands/change-evidence-entry.js.map +1 -0
  30. package/dist/commands/change-evidence.d.ts +108 -0
  31. package/dist/commands/change-evidence.js +341 -0
  32. package/dist/commands/change-evidence.js.map +1 -0
  33. package/dist/commands/change-finish.d.ts +71 -0
  34. package/dist/commands/change-finish.js +387 -0
  35. package/dist/commands/change-finish.js.map +1 -0
  36. package/dist/commands/change-git-status.d.ts +22 -0
  37. package/dist/commands/change-git-status.js +50 -0
  38. package/dist/commands/change-git-status.js.map +1 -0
  39. package/dist/commands/change-lifecycle-entry.d.ts +29 -0
  40. package/{src → dist}/commands/change-lifecycle-entry.js +21 -28
  41. package/dist/commands/change-lifecycle-entry.js.map +1 -0
  42. package/dist/commands/change-lifecycle.d.ts +56 -0
  43. package/dist/commands/change-lifecycle.js +211 -0
  44. package/dist/commands/change-lifecycle.js.map +1 -0
  45. package/dist/commands/change-metrics-command.d.ts +12 -0
  46. package/dist/commands/change-metrics-command.js +110 -0
  47. package/dist/commands/change-metrics-command.js.map +1 -0
  48. package/dist/commands/change-next-command.d.ts +18 -0
  49. package/dist/commands/change-next-command.js +138 -0
  50. package/dist/commands/change-next-command.js.map +1 -0
  51. package/dist/commands/change-review-gates.d.ts +107 -0
  52. package/dist/commands/change-review-gates.js +329 -0
  53. package/dist/commands/change-review-gates.js.map +1 -0
  54. package/dist/commands/change-scope-gate.d.ts +21 -0
  55. package/dist/commands/change-scope-gate.js +32 -0
  56. package/dist/commands/change-scope-gate.js.map +1 -0
  57. package/dist/commands/change-start.d.ts +30 -0
  58. package/dist/commands/change-start.js +91 -0
  59. package/dist/commands/change-start.js.map +1 -0
  60. package/dist/commands/change-state-command.d.ts +3 -0
  61. package/dist/commands/change-state-command.js +32 -0
  62. package/dist/commands/change-state-command.js.map +1 -0
  63. package/dist/commands/change-status-command.d.ts +13 -0
  64. package/dist/commands/change-status-command.js +126 -0
  65. package/dist/commands/change-status-command.js.map +1 -0
  66. package/dist/commands/change-status-context.d.ts +58 -0
  67. package/dist/commands/change-status-context.js +166 -0
  68. package/dist/commands/change-status-context.js.map +1 -0
  69. package/dist/commands/change-task-stats.d.ts +24 -0
  70. package/dist/commands/change-task-stats.js +66 -0
  71. package/dist/commands/change-task-stats.js.map +1 -0
  72. package/dist/commands/change-validate-entry.d.ts +17 -0
  73. package/dist/commands/change-validate-entry.js +44 -0
  74. package/dist/commands/change-validate-entry.js.map +1 -0
  75. package/dist/commands/change-validate.d.ts +32 -0
  76. package/dist/commands/change-validate.js +128 -0
  77. package/dist/commands/change-validate.js.map +1 -0
  78. package/dist/commands/change.d.ts +322 -0
  79. package/dist/commands/change.js +1731 -0
  80. package/dist/commands/change.js.map +1 -0
  81. package/dist/commands/codex-install-prompts.d.ts +4 -0
  82. package/dist/commands/codex-install-prompts.js +68 -0
  83. package/dist/commands/codex-install-prompts.js.map +1 -0
  84. package/dist/commands/codex-install-skills.d.ts +4 -0
  85. package/dist/commands/codex-install-skills.js +74 -0
  86. package/dist/commands/codex-install-skills.js.map +1 -0
  87. package/dist/commands/codex-status-prompts.d.ts +4 -0
  88. package/dist/commands/codex-status-prompts.js +44 -0
  89. package/dist/commands/codex-status-prompts.js.map +1 -0
  90. package/dist/commands/codex-status-skills.d.ts +4 -0
  91. package/dist/commands/codex-status-skills.js +54 -0
  92. package/dist/commands/codex-status-skills.js.map +1 -0
  93. package/dist/commands/codex-uninstall-prompts.d.ts +4 -0
  94. package/dist/commands/codex-uninstall-prompts.js +51 -0
  95. package/dist/commands/codex-uninstall-prompts.js.map +1 -0
  96. package/dist/commands/codex-uninstall-skills.d.ts +4 -0
  97. package/dist/commands/codex-uninstall-skills.js +59 -0
  98. package/dist/commands/codex-uninstall-skills.js.map +1 -0
  99. package/dist/commands/dashboard.d.ts +222 -0
  100. package/dist/commands/dashboard.js +193 -0
  101. package/dist/commands/dashboard.js.map +1 -0
  102. package/dist/commands/hooks-install.d.ts +4 -0
  103. package/dist/commands/hooks-install.js +90 -0
  104. package/dist/commands/hooks-install.js.map +1 -0
  105. package/dist/commands/hooks-status.d.ts +4 -0
  106. package/dist/commands/hooks-status.js +89 -0
  107. package/dist/commands/hooks-status.js.map +1 -0
  108. package/dist/commands/init.d.ts +4 -0
  109. package/dist/commands/init.js +100 -0
  110. package/dist/commands/init.js.map +1 -0
  111. package/dist/commands/opencode-auto.d.ts +11 -0
  112. package/dist/commands/opencode-auto.js +158 -0
  113. package/dist/commands/opencode-auto.js.map +1 -0
  114. package/dist/commands/opencode-status.d.ts +4 -0
  115. package/dist/commands/opencode-status.js +75 -0
  116. package/dist/commands/opencode-status.js.map +1 -0
  117. package/dist/commands/opencode-supervise.d.ts +24 -0
  118. package/dist/commands/opencode-supervise.js +188 -0
  119. package/dist/commands/opencode-supervise.js.map +1 -0
  120. package/dist/commands/rollback.d.ts +4 -0
  121. package/dist/commands/rollback.js +12 -0
  122. package/dist/commands/rollback.js.map +1 -0
  123. package/dist/commands/update.d.ts +4 -0
  124. package/dist/commands/update.js +212 -0
  125. package/dist/commands/update.js.map +1 -0
  126. package/dist/commands/validate.d.ts +4 -0
  127. package/dist/commands/validate.js +222 -0
  128. package/dist/commands/validate.js.map +1 -0
  129. package/dist/commands/ws-commit.d.ts +5 -0
  130. package/dist/commands/ws-commit.js +39 -0
  131. package/dist/commands/ws-commit.js.map +1 -0
  132. package/dist/commands/ws-deliver.d.ts +5 -0
  133. package/dist/commands/ws-deliver.js +59 -0
  134. package/dist/commands/ws-deliver.js.map +1 -0
  135. package/dist/commands/ws-finish.d.ts +5 -0
  136. package/dist/commands/ws-finish.js +142 -0
  137. package/dist/commands/ws-finish.js.map +1 -0
  138. package/dist/commands/ws-handoff.d.ts +5 -0
  139. package/dist/commands/ws-handoff.js +35 -0
  140. package/dist/commands/ws-handoff.js.map +1 -0
  141. package/dist/commands/ws-migrate.d.ts +5 -0
  142. package/dist/commands/ws-migrate.js +41 -0
  143. package/dist/commands/ws-migrate.js.map +1 -0
  144. package/dist/commands/ws-plan-verify.d.ts +5 -0
  145. package/dist/commands/ws-plan-verify.js +38 -0
  146. package/dist/commands/ws-plan-verify.js.map +1 -0
  147. package/dist/commands/ws-pull.d.ts +5 -0
  148. package/dist/commands/ws-pull.js +26 -0
  149. package/dist/commands/ws-pull.js.map +1 -0
  150. package/dist/commands/ws-push.d.ts +5 -0
  151. package/dist/commands/ws-push.js +68 -0
  152. package/dist/commands/ws-push.js.map +1 -0
  153. package/dist/commands/ws-submodule-setup.d.ts +5 -0
  154. package/dist/commands/ws-submodule-setup.js +55 -0
  155. package/dist/commands/ws-submodule-setup.js.map +1 -0
  156. package/dist/commands/ws-verify-before-complete.d.ts +5 -0
  157. package/dist/commands/ws-verify-before-complete.js +35 -0
  158. package/dist/commands/ws-verify-before-complete.js.map +1 -0
  159. package/dist/dashboard/app.d.ts +1 -0
  160. package/dist/dashboard/app.js +370 -0
  161. package/dist/dashboard/app.js.map +1 -0
  162. package/dist/delegation/cli.d.ts +6 -0
  163. package/dist/delegation/cli.js +14 -0
  164. package/dist/delegation/cli.js.map +1 -0
  165. package/dist/delegation/index.d.ts +35 -0
  166. package/dist/delegation/index.js +118 -0
  167. package/dist/delegation/index.js.map +1 -0
  168. package/dist/errors.d.ts +8 -0
  169. package/dist/errors.js +11 -0
  170. package/dist/errors.js.map +1 -0
  171. package/dist/exec.d.ts +9 -0
  172. package/dist/exec.js +22 -0
  173. package/dist/exec.js.map +1 -0
  174. package/dist/fs.d.ts +9 -0
  175. package/dist/fs.js +62 -0
  176. package/dist/fs.js.map +1 -0
  177. package/dist/governance.d.ts +10 -0
  178. package/dist/governance.js +135 -0
  179. package/dist/governance.js.map +1 -0
  180. package/dist/hash.d.ts +3 -0
  181. package/dist/hash.js +13 -0
  182. package/dist/hash.js.map +1 -0
  183. package/dist/hooks/deploy-claude.d.ts +13 -0
  184. package/dist/hooks/deploy-claude.js +67 -0
  185. package/dist/hooks/deploy-claude.js.map +1 -0
  186. package/dist/journal.d.ts +22 -0
  187. package/dist/journal.js +112 -0
  188. package/dist/journal.js.map +1 -0
  189. package/dist/json-schema-lite.d.ts +22 -0
  190. package/dist/json-schema-lite.js +148 -0
  191. package/dist/json-schema-lite.js.map +1 -0
  192. package/dist/managed-blocks.d.ts +24 -0
  193. package/dist/managed-blocks.js +84 -0
  194. package/dist/managed-blocks.js.map +1 -0
  195. package/dist/manifest.d.ts +24 -0
  196. package/dist/manifest.js +145 -0
  197. package/dist/manifest.js.map +1 -0
  198. package/dist/opencode-env.d.ts +62 -0
  199. package/dist/opencode-env.js +178 -0
  200. package/dist/opencode-env.js.map +1 -0
  201. package/dist/path-utils.d.ts +2 -0
  202. package/dist/path-utils.js +8 -0
  203. package/dist/path-utils.js.map +1 -0
  204. package/dist/spec.d.ts +17 -0
  205. package/dist/spec.js +174 -0
  206. package/dist/spec.js.map +1 -0
  207. package/dist/template.d.ts +20 -0
  208. package/dist/template.js +123 -0
  209. package/dist/template.js.map +1 -0
  210. package/dist/workspace.d.ts +4 -0
  211. package/dist/workspace.js +19 -0
  212. package/dist/workspace.js.map +1 -0
  213. package/package.json +11 -3
  214. package/src/aiws-package.js +0 -15
  215. package/src/backup.js +0 -149
  216. package/src/cli.js +0 -647
  217. package/src/codex-prompts.js +0 -74
  218. package/src/codex-skills.js +0 -111
  219. package/src/commands/change-advice.js +0 -200
  220. package/src/commands/change-evidence-command.js +0 -242
  221. package/src/commands/change-evidence-entry.js +0 -40
  222. package/src/commands/change-evidence.js +0 -340
  223. package/src/commands/change-finish.js +0 -484
  224. package/src/commands/change-git-status.js +0 -53
  225. package/src/commands/change-lifecycle.js +0 -236
  226. package/src/commands/change-metrics-command.js +0 -110
  227. package/src/commands/change-next-command.js +0 -129
  228. package/src/commands/change-review-gates.js +0 -315
  229. package/src/commands/change-scope-gate.js +0 -34
  230. package/src/commands/change-start.js +0 -120
  231. package/src/commands/change-state-command.js +0 -40
  232. package/src/commands/change-status-command.js +0 -134
  233. package/src/commands/change-status-context.js +0 -202
  234. package/src/commands/change-task-stats.js +0 -64
  235. package/src/commands/change-validate-entry.js +0 -45
  236. package/src/commands/change-validate.js +0 -127
  237. package/src/commands/change.js +0 -2087
  238. package/src/commands/codex-install-prompts.js +0 -68
  239. package/src/commands/codex-install-skills.js +0 -74
  240. package/src/commands/codex-status-prompts.js +0 -55
  241. package/src/commands/codex-status-skills.js +0 -63
  242. package/src/commands/codex-uninstall-prompts.js +0 -55
  243. package/src/commands/codex-uninstall-skills.js +0 -62
  244. package/src/commands/dashboard.js +0 -205
  245. package/src/commands/hooks-install.js +0 -92
  246. package/src/commands/hooks-status.js +0 -95
  247. package/src/commands/init.js +0 -93
  248. package/src/commands/opencode-status.js +0 -61
  249. package/src/commands/rollback.js +0 -13
  250. package/src/commands/update.js +0 -133
  251. package/src/commands/validate.js +0 -222
  252. package/src/dashboard/app.js +0 -371
  253. package/src/errors.js +0 -15
  254. package/src/exec.js +0 -34
  255. package/src/fs.js +0 -91
  256. package/src/governance.js +0 -159
  257. package/src/hash.js +0 -25
  258. package/src/json-schema-lite.js +0 -164
  259. package/src/managed-blocks.js +0 -131
  260. package/src/manifest.js +0 -153
  261. package/src/opencode-env.js +0 -76
  262. package/src/path-utils.js +0 -20
  263. package/src/spec.js +0 -195
  264. package/src/template.js +0 -187
  265. package/src/workspace.js +0 -23
  266. /package/{src → dist}/dashboard/index.html +0 -0
  267. /package/{src → dist}/dashboard/style.css +0 -0
@@ -1,484 +0,0 @@
1
- import path from "node:path";
2
-
3
- /**
4
- * @param {string} gitRoot
5
- * @param {string} branch
6
- * @param {string | undefined} preferredRemote
7
- * @param {{ runCommand: typeof import("../exec.js").runCommand }} deps
8
- */
9
- export async function resolvePushTarget(gitRoot, branch, preferredRemote, deps) {
10
- const remoteRaw = String(preferredRemote || "").trim();
11
- const branchRemoteRes = await deps.runCommand("git", ["config", "--get", `branch.${branch}.remote`], { cwd: gitRoot });
12
- const trackedRemote = String(branchRemoteRes.stdout || "").trim();
13
-
14
- const branchMergeRes = await deps.runCommand("git", ["config", "--get", `branch.${branch}.merge`], { cwd: gitRoot });
15
- const trackedMergeRef = String(branchMergeRes.stdout || "").trim();
16
- const trackedBranch = trackedMergeRef.startsWith("refs/heads/") ? trackedMergeRef.slice("refs/heads/".length) : "";
17
-
18
- const remote = remoteRaw || trackedRemote || "origin";
19
- const remoteBranch = trackedBranch || branch;
20
- const refspec = remoteBranch === branch ? branch : `${branch}:${remoteBranch}`;
21
-
22
- return { remote, remoteBranch, refspec, trackedRemote, trackedMergeRef };
23
- }
24
-
25
- /**
26
- * @param {string} gitRoot
27
- * @param {string} worktreePath
28
- * @param {{
29
- * checkGitClean: (gitRoot: string) => Promise<{ clean: true } | { clean: false, details: string }>,
30
- * listGitWorktrees: (gitRoot: string) => Promise<Array<{ worktree: string, locked: boolean, lockReason: string }>>,
31
- * pathExists: (path: string) => Promise<boolean>,
32
- * runCommand: typeof import("../exec.js").runCommand,
33
- * UserError: typeof import("../errors.js").UserError
34
- * }} deps
35
- */
36
- export async function cleanupWorktreePath(gitRoot, worktreePath, deps) {
37
- const abs = path.resolve(worktreePath);
38
- if (abs === path.resolve(gitRoot)) {
39
- return { status: "skipped", reason: "current_worktree", worktree: abs };
40
- }
41
-
42
- const worktrees = await deps.listGitWorktrees(gitRoot);
43
- const worktreeEntry = worktrees.find((worktree) => path.resolve(worktree.worktree) === abs);
44
- if (worktreeEntry?.locked) {
45
- return { status: "skipped", reason: "locked", worktree: abs, details: worktreeEntry.lockReason };
46
- }
47
-
48
- if (!(await deps.pathExists(abs))) {
49
- const prune = await deps.runCommand("git", ["worktree", "prune"], { cwd: gitRoot });
50
- if (prune.code !== 0) {
51
- throw new deps.UserError("Failed to prune stale git worktree metadata.", { details: prune.stderr || prune.stdout });
52
- }
53
- return { status: "pruned-missing", worktree: abs };
54
- }
55
-
56
- const clean = await deps.checkGitClean(abs);
57
- if (!clean.clean) {
58
- return { status: "skipped", reason: "dirty", worktree: abs, details: clean.details };
59
- }
60
-
61
- let rm = await deps.runCommand("git", ["worktree", "remove", abs], { cwd: gitRoot });
62
- if (rm.code !== 0) {
63
- const out = `${rm.stderr || ""}\n${rm.stdout || ""}`;
64
- if (out.includes("working trees containing submodules cannot be moved or removed")) {
65
- rm = await deps.runCommand("git", ["worktree", "remove", "--force", abs], { cwd: gitRoot });
66
- }
67
- }
68
- if (rm.code !== 0) {
69
- throw new deps.UserError("Failed to remove change worktree.", { details: rm.stderr || rm.stdout });
70
- }
71
-
72
- const prune = await deps.runCommand("git", ["worktree", "prune"], { cwd: gitRoot });
73
- if (prune.code !== 0) {
74
- throw new deps.UserError("Failed to prune git worktree metadata after remove.", { details: prune.stderr || prune.stdout });
75
- }
76
-
77
- return { status: "removed", worktree: abs };
78
- }
79
-
80
- /**
81
- * @param {string} gitRoot
82
- * @param {{ pathExists: (path: string) => Promise<boolean>, runCommand: typeof import("../exec.js").runCommand }} deps
83
- * @returns {Promise<Array<{ name: string, path: string }>>}
84
- */
85
- export async function listSubmodulesFromGitmodules(gitRoot, deps) {
86
- const gitmodules = path.join(gitRoot, ".gitmodules");
87
- if (!(await deps.pathExists(gitmodules))) return [];
88
-
89
- const list = await deps.runCommand("git", ["config", "--file", ".gitmodules", "--get-regexp", "^submodule\\..*\\.path$"], { cwd: gitRoot });
90
- if (list.code !== 0) return [];
91
-
92
- /** @type {Array<{ name: string, path: string }>} */
93
- const subs = [];
94
- for (const raw of String(list.stdout || "").split("\n")) {
95
- const line = raw.trim();
96
- if (!line) continue;
97
- const idx = line.indexOf(" ");
98
- if (idx <= 0) continue;
99
- const key = line.slice(0, idx).trim();
100
- const subPath = line.slice(idx + 1).trim();
101
- const match = key.match(/^submodule\.(.+)\.path$/);
102
- if (!match) continue;
103
- const name = String(match[1] || "").trim();
104
- if (!name || !subPath) continue;
105
- subs.push({ name, path: subPath });
106
- }
107
- return subs;
108
- }
109
-
110
- /**
111
- * @param {string} targetsPath
112
- * @param {{ pathExists: (path: string) => Promise<boolean>, readText: (path: string) => Promise<string> }} deps
113
- */
114
- export async function parseSubmoduleTargetsFile(targetsPath, deps) {
115
- /** @type {Map<string, { branch: string, remote: string }>} */
116
- const parsed = new Map();
117
- /** @type {string[]} */
118
- const errors = [];
119
-
120
- if (!(await deps.pathExists(targetsPath))) {
121
- return { parsed, errors };
122
- }
123
-
124
- const text = await deps.readText(targetsPath);
125
- const lines = String(text || "").split(/\r?\n/);
126
- for (let i = 0; i < lines.length; i += 1) {
127
- const raw = lines[i] || "";
128
- const line = raw.trim();
129
- if (!line || line.startsWith("#")) continue;
130
- const parts = line.split(/\s+/).filter(Boolean);
131
- if (parts.length < 2) {
132
- errors.push(`${path.basename(targetsPath)}:${i + 1} invalid line (expected: <path> <target_branch> [remote])`);
133
- continue;
134
- }
135
- const subPath = String(parts[0] || "").trim();
136
- const targetBranch = String(parts[1] || "").trim();
137
- const remote = String(parts[2] || "origin").trim() || "origin";
138
- if (!subPath) {
139
- errors.push(`${path.basename(targetsPath)}:${i + 1} missing submodule path`);
140
- continue;
141
- }
142
- if (parsed.has(subPath)) {
143
- errors.push(`${path.basename(targetsPath)}:${i + 1} duplicate submodule path: ${subPath}`);
144
- continue;
145
- }
146
- if (!targetBranch || ["<fill-me>", "<fill_me>", "tbd", "TBD"].includes(targetBranch)) {
147
- errors.push(`${path.basename(targetsPath)}:${i + 1} missing/placeholder target_branch for ${subPath}`);
148
- continue;
149
- }
150
- parsed.set(subPath, { branch: targetBranch, remote });
151
- }
152
-
153
- return { parsed, errors };
154
- }
155
-
156
- /**
157
- * @param {string} repoPath
158
- * @param {string} rev
159
- * @param {{ pathExists: (path: string) => Promise<boolean>, runCommand: typeof import("../exec.js").runCommand }} deps
160
- */
161
- async function gitHasRevision(repoPath, rev, deps) {
162
- if (!(await deps.pathExists(repoPath))) return false;
163
- const res = await deps.runCommand("git", ["-C", repoPath, "cat-file", "-e", `${rev}^{commit}`], { cwd: repoPath });
164
- return res.code === 0;
165
- }
166
-
167
- /**
168
- * @param {string} repoPath
169
- * @param {{ pathExists: (path: string) => Promise<boolean>, runCommand: typeof import("../exec.js").runCommand }} deps
170
- * @returns {Promise<boolean>}
171
- */
172
- export async function isGitRepository(repoPath, deps) {
173
- if (!(await deps.pathExists(repoPath))) return false;
174
- const res = await deps.runCommand("git", ["-C", repoPath, "rev-parse", "--git-dir"], { cwd: repoPath });
175
- return res.code === 0;
176
- }
177
-
178
- /**
179
- * @param {string} targetRepoPath
180
- * @param {string} sha
181
- * @param {string | undefined} fallbackRepoPath
182
- * @param {{
183
- * pathExists: (path: string) => Promise<boolean>,
184
- * runCommand: typeof import("../exec.js").runCommand,
185
- * UserError: typeof import("../errors.js").UserError
186
- * }} deps
187
- */
188
- async function ensureRevisionAvailable(targetRepoPath, sha, fallbackRepoPath, deps) {
189
- if (await gitHasRevision(targetRepoPath, sha, deps)) return;
190
- const fallback = String(fallbackRepoPath || "").trim();
191
- if (!fallback) {
192
- throw new deps.UserError("Required submodule commit is not available locally.", {
193
- details: `repo: ${targetRepoPath}\ncommit: ${sha}`,
194
- });
195
- }
196
- if (!(await gitHasRevision(fallback, sha, deps))) {
197
- throw new deps.UserError("Required submodule commit is missing in both target and change worktrees.", {
198
- details: `target_repo: ${targetRepoPath}\nchange_repo: ${fallback}\ncommit: ${sha}`,
199
- });
200
- }
201
- const fetch = await deps.runCommand("git", ["-C", targetRepoPath, "fetch", fallback, sha], { cwd: targetRepoPath });
202
- if (fetch.code !== 0 || !(await gitHasRevision(targetRepoPath, sha, deps))) {
203
- throw new deps.UserError("Failed to import submodule commit from change worktree.", {
204
- details: `target_repo: ${targetRepoPath}\nchange_repo: ${fallback}\ncommit: ${sha}\n\n${fetch.stderr || fetch.stdout}`,
205
- });
206
- }
207
- }
208
-
209
- /**
210
- * @param {string} repoPath
211
- * @param {string} remote
212
- * @param {string} branch
213
- * @param {string} sha
214
- * @param {{ runCommand: typeof import("../exec.js").runCommand, UserError: typeof import("../errors.js").UserError }} deps
215
- */
216
- async function pushSubmoduleBranch(repoPath, remote, branch, sha, deps) {
217
- const fetch = await deps.runCommand("git", ["-C", repoPath, "fetch", remote, "--prune"], { cwd: repoPath });
218
- if (fetch.code !== 0) {
219
- throw new deps.UserError("Failed to fetch submodule remote before push.", {
220
- details: `repo: ${repoPath}\nremote: ${remote}\n\n${fetch.stderr || fetch.stdout}`,
221
- });
222
- }
223
-
224
- const remoteRef = `refs/remotes/${remote}/${branch}`;
225
- const hasRemoteBranch = await deps
226
- .runCommand("git", ["-C", repoPath, "show-ref", "--verify", "--quiet", remoteRef], { cwd: repoPath })
227
- .then((result) => result.code === 0);
228
- if (!hasRemoteBranch) {
229
- throw new deps.UserError("Submodule target branch does not exist on remote.", {
230
- details: `repo: ${repoPath}\nremote: ${remote}\nbranch: ${branch}`,
231
- });
232
- }
233
-
234
- const remoteAncestor = await deps
235
- .runCommand("git", ["-C", repoPath, "merge-base", "--is-ancestor", remoteRef, sha], { cwd: repoPath })
236
- .then((result) => result.code === 0);
237
- if (remoteAncestor) {
238
- const push = await deps.runCommand("git", ["-C", repoPath, "push", remote, `${sha}:refs/heads/${branch}`], { cwd: repoPath });
239
- if (push.code !== 0) {
240
- throw new deps.UserError("Failed to fast-forward push submodule target branch.", {
241
- details: `repo: ${repoPath}\nremote: ${remote}\nbranch: ${branch}\ncommit: ${sha}\n\n${push.stderr || push.stdout}`,
242
- });
243
- }
244
- return { status: "pushed", remoteRef };
245
- }
246
-
247
- const shaContained = await deps
248
- .runCommand("git", ["-C", repoPath, "merge-base", "--is-ancestor", sha, remoteRef], { cwd: repoPath })
249
- .then((result) => result.code === 0);
250
- if (shaContained) {
251
- return { status: "already-contained", remoteRef };
252
- }
253
-
254
- throw new deps.UserError("Submodule target branch diverged from gitlink commit.", {
255
- details: `repo: ${repoPath}\nremote: ${remote}\nbranch: ${branch}\ncommit: ${sha}\n\nHint: reconcile the submodule branch manually, then retry.`,
256
- });
257
- }
258
-
259
- /**
260
- * @param {string} gitRoot
261
- * @param {string} changeId
262
- * @param {string} baseBranch
263
- * @param {string | undefined} changeWorktreePath
264
- * @param {{
265
- * pathExists: (path: string) => Promise<boolean>,
266
- * readText: (path: string) => Promise<string>,
267
- * runCommand: typeof import("../exec.js").runCommand,
268
- * UserError: typeof import("../errors.js").UserError
269
- * }} deps
270
- */
271
- export async function pushSubmodulesForFinish(gitRoot, changeId, baseBranch, changeWorktreePath, deps) {
272
- const subs = await listSubmodulesFromGitmodules(gitRoot, deps);
273
- if (subs.length === 0) return { processed: 0 };
274
-
275
- const targetsPath = path.join(gitRoot, "changes", changeId, "submodules.targets");
276
- if (!(await deps.pathExists(targetsPath))) {
277
- throw new deps.UserError("Missing submodules.targets for submodule-aware finish.", {
278
- details: `required: ${path.relative(gitRoot, targetsPath)}\n\nHint: add one entry per submodule path before running \`aiws change finish --push\`.`,
279
- });
280
- }
281
-
282
- const parsedTargets = await parseSubmoduleTargetsFile(targetsPath, deps);
283
- if (parsedTargets.errors.length > 0) {
284
- throw new deps.UserError("Invalid submodules.targets.", { details: parsedTargets.errors.join("\n") });
285
- }
286
-
287
- const missingPaths = subs.filter((submodule) => !parsedTargets.parsed.has(submodule.path)).map((submodule) => submodule.path);
288
- if (missingPaths.length > 0) {
289
- throw new deps.UserError("submodules.targets missing declared submodule paths.", {
290
- details: `file: ${path.relative(gitRoot, targetsPath)}\nmissing: ${missingPaths.join(", ")}`,
291
- });
292
- }
293
-
294
- /** @type {Array<{ name: string, path: string, sha: string, branch: string, remote: string, pinBranch: string }>} */
295
- const specs = [];
296
- for (const sub of subs) {
297
- const target = parsedTargets.parsed.get(sub.path);
298
- if (!target) continue;
299
- const targetBranch = target.branch === "." ? baseBranch : target.branch;
300
- const gitlink = await deps.runCommand("git", ["rev-parse", `HEAD:${sub.path}`], { cwd: gitRoot });
301
- if (gitlink.code !== 0) {
302
- throw new deps.UserError("Failed to resolve submodule gitlink commit from merged superproject.", {
303
- details: `path: ${sub.path}\n\n${gitlink.stderr || gitlink.stdout}`,
304
- });
305
- }
306
- const sha = String(gitlink.stdout || "").trim();
307
- specs.push({
308
- name: sub.name,
309
- path: sub.path,
310
- sha,
311
- branch: targetBranch,
312
- remote: target.remote,
313
- pinBranch: `aiws/pin/${targetBranch}`,
314
- });
315
- }
316
-
317
- for (const spec of specs) {
318
- const targetRepoPath = path.join(gitRoot, spec.path);
319
- const changeRepoPath = changeWorktreePath ? path.join(changeWorktreePath, spec.path) : "";
320
- const targetRepoReady = await isGitRepository(targetRepoPath, deps);
321
- const changeRepoReady = changeRepoPath ? await isGitRepository(changeRepoPath, deps) : false;
322
- if (!targetRepoReady && !changeRepoReady) {
323
- throw new deps.UserError("Submodule repository is not initialized in either target or change worktree.", {
324
- details: `path: ${spec.path}\nHint: initialize submodules before finish, or keep using worktree mode for changes involving submodules.`,
325
- });
326
- }
327
-
328
- const sourceRepoPath =
329
- (targetRepoReady && (await gitHasRevision(targetRepoPath, spec.sha, deps))) ? targetRepoPath : changeRepoReady ? changeRepoPath : targetRepoPath;
330
- if (!(await gitHasRevision(sourceRepoPath, spec.sha, deps))) {
331
- throw new deps.UserError("Merged submodule gitlink commit is not available locally.", {
332
- details: `path: ${spec.path}\ncommit: ${spec.sha}\nsource_repo: ${sourceRepoPath}`,
333
- });
334
- }
335
-
336
- const pushResult = await pushSubmoduleBranch(sourceRepoPath, spec.remote, spec.branch, spec.sha, deps);
337
- console.log(
338
- `submodule push: ${spec.path} (${spec.remote} ${spec.branch}${pushResult.status === "pushed" ? ` <= ${spec.sha}` : ", already contained"})`,
339
- );
340
- }
341
-
342
- const sync = await deps.runCommand("git", ["submodule", "update", "--init", "--recursive"], { cwd: gitRoot });
343
- if (sync.code !== 0) {
344
- throw new deps.UserError("Submodule push succeeded, but failed to sync target worktree submodules.", {
345
- details: sync.stderr || sync.stdout,
346
- });
347
- }
348
-
349
- for (const spec of specs) {
350
- const targetRepoPath = path.join(gitRoot, spec.path);
351
- if (!(await isGitRepository(targetRepoPath, deps))) {
352
- throw new deps.UserError("Submodule repo missing after sync.", { details: `path: ${spec.path}` });
353
- }
354
- await ensureRevisionAvailable(targetRepoPath, spec.sha, changeWorktreePath ? path.join(changeWorktreePath, spec.path) : "", deps);
355
- const fetch = await deps.runCommand("git", ["-C", targetRepoPath, "fetch", spec.remote, "--prune"], { cwd: targetRepoPath });
356
- if (fetch.code !== 0) {
357
- throw new deps.UserError("Failed to refresh target worktree submodule remote.", {
358
- details: `path: ${spec.path}\nremote: ${spec.remote}\n\n${fetch.stderr || fetch.stdout}`,
359
- });
360
- }
361
- const checkout = await deps.runCommand("git", ["-C", targetRepoPath, "checkout", "-B", spec.pinBranch, spec.sha], { cwd: targetRepoPath });
362
- if (checkout.code !== 0) {
363
- throw new deps.UserError("Failed to attach target worktree submodule to pin branch.", {
364
- details: `path: ${spec.path}\npin_branch: ${spec.pinBranch}\ncommit: ${spec.sha}\n\n${checkout.stderr || checkout.stdout}`,
365
- });
366
- }
367
- await deps.runCommand("git", ["-C", targetRepoPath, "branch", "--set-upstream-to", `${spec.remote}/${spec.branch}`, spec.pinBranch], {
368
- cwd: targetRepoPath,
369
- });
370
- }
371
-
372
- return { processed: specs.length };
373
- }
374
-
375
- /**
376
- * @param {string} gitRoot
377
- * @param {{ changeId?: string, into?: string, base?: string }} options
378
- * @param {{
379
- * assertValidChangeId: (changeId: string) => void,
380
- * checkGitClean: (gitRoot: string) => Promise<{ clean: true } | { clean: false, details: string }>,
381
- * currentBranch: (gitRoot: string) => Promise<string>,
382
- * listGitWorktrees: (gitRoot: string) => Promise<Array<{ worktree: string, branch: string }>>,
383
- * pathExists: (path: string) => Promise<boolean>,
384
- * readText: (path: string) => Promise<string>,
385
- * resolveChangeId: (gitRoot: string, changeId: string | undefined, options: { command: string }) => Promise<string>,
386
- * runCommand: typeof import("../exec.js").runCommand,
387
- * UserError: typeof import("../errors.js").UserError
388
- * }} deps
389
- */
390
- export async function resolveFinishContext(gitRoot, options, deps) {
391
- const changeId = await deps.resolveChangeId(gitRoot, options.changeId, { command: "finish" });
392
- deps.assertValidChangeId(changeId);
393
-
394
- const changeBranch = `change/${changeId}`;
395
- const changeBranchRef = `refs/heads/${changeBranch}`;
396
-
397
- const hasChangeBranch = await deps
398
- .runCommand("git", ["show-ref", "--verify", "--quiet", changeBranchRef], { cwd: gitRoot })
399
- .then((result) => result.code === 0);
400
- if (!hasChangeBranch) {
401
- throw new deps.UserError(`Missing change branch: ${changeBranch}`, {
402
- details: "Hint: create it with `aiws change start <change-id>` (or `git switch -c change/<id>`).",
403
- });
404
- }
405
-
406
- const clean = await deps.checkGitClean(gitRoot);
407
- if (!clean.clean) {
408
- throw new deps.UserError("Refusing to finish with a dirty working tree.", {
409
- details: `${clean.details}\n\nHint: commit or stash your changes, then retry.`,
410
- });
411
- }
412
-
413
- const cur = await deps.currentBranch(gitRoot);
414
- const intoRaw = String(options.into || "").trim();
415
- const baseRaw = String(options.base || "").trim();
416
- if (intoRaw && baseRaw && intoRaw !== baseRaw) {
417
- throw new deps.UserError("change finish: cannot combine --into with --base (values differ).", {
418
- details: `--into=${intoRaw}\n--base=${baseRaw}`,
419
- });
420
- }
421
-
422
- /** @type {string} */
423
- let into = intoRaw || baseRaw;
424
-
425
- if (!into) {
426
- if (!cur) {
427
- throw new deps.UserError("Detached HEAD: cannot infer target branch for finish.", {
428
- details: "Hint: switch to the target branch (e.g. main) and retry, or pass `aiws change finish <id> --into <branch>`.",
429
- });
430
- }
431
- if (cur !== changeBranch) {
432
- into = cur;
433
- } else {
434
- const metaPath = path.join(gitRoot, "changes", changeId, ".ws-change.json");
435
- /** @type {any} */
436
- let meta = null;
437
- if (await deps.pathExists(metaPath)) {
438
- try {
439
- meta = JSON.parse(await deps.readText(metaPath));
440
- } catch {
441
- meta = null;
442
- }
443
- }
444
- if (!meta) {
445
- const show = await deps.runCommand("git", ["show", `${changeBranch}:changes/${changeId}/.ws-change.json`], { cwd: gitRoot });
446
- if (show.code === 0) {
447
- try {
448
- meta = JSON.parse(String(show.stdout || ""));
449
- } catch {
450
- meta = null;
451
- }
452
- }
453
- }
454
- const inferred = meta && typeof meta === "object" ? String(meta.base_branch || "").trim() : "";
455
- if (!inferred) {
456
- throw new deps.UserError("Cannot infer base branch for finish.", {
457
- details: "Hint: run from the target branch (e.g. main), or pass: `aiws change finish <id> --into <branch>`.",
458
- });
459
- }
460
- into = inferred;
461
- }
462
- }
463
-
464
- if (into === changeBranch) {
465
- throw new deps.UserError("change finish: target branch cannot be the change branch.", { details: `target=${into}` });
466
- }
467
-
468
- const worktrees = await deps.listGitWorktrees(gitRoot);
469
- const intoRef = `refs/heads/${into}`;
470
- const intoWt = worktrees.find((worktree) => String(worktree.branch || "") === intoRef);
471
- if (intoWt && path.resolve(intoWt.worktree) !== path.resolve(gitRoot)) {
472
- throw new deps.UserError("Target branch is checked out in another worktree.", {
473
- details: `branch: ${into}\nworktree: ${intoWt.worktree}\n\nHint: run finish in that worktree:\n cd ${intoWt.worktree}\n aiws change finish ${changeId}`,
474
- });
475
- }
476
-
477
- const hasIntoBranch = await deps.runCommand("git", ["show-ref", "--verify", "--quiet", intoRef], { cwd: gitRoot }).then((result) => result.code === 0);
478
- if (!hasIntoBranch) {
479
- throw new deps.UserError("Target branch does not exist.", { details: `branch: ${into}` });
480
- }
481
-
482
- const changeWt = worktrees.find((worktree) => String(worktree.branch || "") === changeBranchRef);
483
- return { changeId, changeBranch, changeBranchRef, into, cur, worktrees, changeWt };
484
- }
@@ -1,53 +0,0 @@
1
- import { runCommand } from "../exec.js";
2
- import { UserError } from "../errors.js";
3
-
4
- const CONFLICT_STATUS_RE = /^(DD|AU|UD|UA|DU|AA|UU)$/;
5
-
6
- /**
7
- * @param {string} output
8
- */
9
- export function parseGitStatusPorcelain(output) {
10
- const lines = String(output || "")
11
- .split(/\r?\n/)
12
- .map((line) => line.trimEnd())
13
- .filter(Boolean);
14
-
15
- let staged = 0;
16
- let unstaged = 0;
17
- let untracked = 0;
18
- let conflicted = 0;
19
-
20
- for (const line of lines) {
21
- const xy = line.slice(0, 2);
22
- if (xy === "??") {
23
- untracked += 1;
24
- continue;
25
- }
26
- if (CONFLICT_STATUS_RE.test(xy)) {
27
- conflicted += 1;
28
- continue;
29
- }
30
- if (xy[0] && xy[0] !== " " && xy[0] !== "?") staged += 1;
31
- if (xy[1] && xy[1] !== " ") unstaged += 1;
32
- }
33
-
34
- return {
35
- clean: lines.length === 0,
36
- total: lines.length,
37
- staged,
38
- unstaged,
39
- untracked,
40
- conflicted,
41
- };
42
- }
43
-
44
- /**
45
- * @param {string} gitRoot
46
- */
47
- export async function collectGitStatus(gitRoot) {
48
- const res = await runCommand("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
49
- if (res.code !== 0) {
50
- throw new UserError("Failed to inspect git status for change governance.", { details: res.stderr || res.stdout });
51
- }
52
- return parseGitStatusPorcelain(res.stdout || "");
53
- }