@open-agent-toolkit/cli 0.2.41 → 0.2.46

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 (212) hide show
  1. package/assets/agents/oat-phase-implementer.md +11 -1
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/cli-utilities/configuration.md +11 -1
  4. package/assets/docs/cli-utilities/tool-packs.md +55 -9
  5. package/assets/docs/quickstart.md +9 -0
  6. package/assets/docs/reference/cli-reference.md +56 -1
  7. package/assets/docs/reference/file-locations.md +18 -1
  8. package/assets/docs/reference/oat-directory-structure.md +20 -4
  9. package/assets/docs/reference/troubleshooting.md +18 -11
  10. package/assets/docs/workflows/projects/artifacts.md +27 -1
  11. package/assets/docs/workflows/projects/implementation-execution.md +22 -0
  12. package/assets/docs/workflows/projects/index.md +6 -0
  13. package/assets/docs/workflows/projects/lifecycle.md +19 -5
  14. package/assets/docs/workflows/projects/picking-up-projects.md +75 -0
  15. package/assets/docs/workflows/projects/pr-flow.md +22 -1
  16. package/assets/docs/workflows/projects/reviewing-oat-prs.md +60 -0
  17. package/assets/public-package-versions.json +4 -4
  18. package/assets/skills/oat-brainstorm/SKILL.md +135 -32
  19. package/assets/skills/oat-brainstorm/references/destinations.md +9 -6
  20. package/assets/skills/oat-cursor-cloud-projects/SKILL.md +11 -1
  21. package/assets/skills/oat-doctor/SKILL.md +8 -1
  22. package/assets/skills/oat-project-autonomous/SKILL.md +45 -1
  23. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +8 -8
  24. package/assets/skills/oat-project-capture/SKILL.md +29 -9
  25. package/assets/skills/oat-project-complete/SKILL.md +610 -38
  26. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +265 -0
  27. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +764 -0
  28. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +134 -0
  29. package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +160 -0
  30. package/assets/skills/oat-project-design/SKILL.md +33 -9
  31. package/assets/skills/oat-project-discover/SKILL.md +16 -3
  32. package/assets/skills/oat-project-document/SKILL.md +10 -4
  33. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +8 -8
  34. package/assets/skills/oat-project-implement/SKILL.md +14 -3
  35. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +18 -6
  36. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +8 -8
  37. package/assets/skills/oat-project-implement/references/phase-execution.md +9 -3
  38. package/assets/skills/oat-project-implement/references/plan-and-resume.md +3 -0
  39. package/assets/skills/oat-project-import-plan/SKILL.md +15 -9
  40. package/assets/skills/oat-project-new/SKILL.md +7 -5
  41. package/assets/skills/oat-project-next/SKILL.md +32 -10
  42. package/assets/skills/oat-project-plan/SKILL.md +18 -3
  43. package/assets/skills/oat-project-pr-final/SKILL.md +23 -2
  44. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +8 -8
  45. package/assets/skills/oat-project-pr-progress/SKILL.md +23 -2
  46. package/assets/skills/oat-project-progress/SKILL.md +23 -9
  47. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +20 -2
  48. package/assets/skills/oat-project-quick-start/SKILL.md +48 -18
  49. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +8 -8
  50. package/assets/skills/oat-project-reconcile/SKILL.md +14 -10
  51. package/assets/skills/oat-project-retro/SKILL.md +3 -3
  52. package/assets/skills/oat-project-retro/references/apply-procedure.md +102 -6
  53. package/assets/skills/oat-project-retro-file/SKILL.md +57 -6
  54. package/assets/skills/oat-project-review-provide/SKILL.md +45 -4
  55. package/assets/skills/oat-project-review-receive/SKILL.md +16 -7
  56. package/assets/skills/oat-project-review-receive-remote/SKILL.md +9 -3
  57. package/assets/skills/oat-project-revise/SKILL.md +12 -3
  58. package/assets/skills/oat-project-spec/SKILL.md +16 -3
  59. package/assets/skills/oat-project-summary/SKILL.md +113 -13
  60. package/assets/skills/oat-wave-execute/SKILL.md +34 -2
  61. package/assets/skills/oat-worktree-bootstrap/SKILL.md +13 -1
  62. package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +13 -1
  63. package/dist/commands/config/index.d.ts +1 -0
  64. package/dist/commands/config/index.d.ts.map +1 -1
  65. package/dist/commands/config/index.js +34 -3
  66. package/dist/commands/doctor/index.d.ts +2 -0
  67. package/dist/commands/doctor/index.d.ts.map +1 -1
  68. package/dist/commands/doctor/index.js +27 -3
  69. package/dist/commands/doctor/synced-projects.d.ts +12 -0
  70. package/dist/commands/doctor/synced-projects.d.ts.map +1 -0
  71. package/dist/commands/doctor/synced-projects.js +208 -0
  72. package/dist/commands/init/gitattributes.d.ts +4 -0
  73. package/dist/commands/init/gitattributes.d.ts.map +1 -0
  74. package/dist/commands/init/gitattributes.js +12 -0
  75. package/dist/commands/init/gitignore.d.ts +8 -1
  76. package/dist/commands/init/gitignore.d.ts.map +1 -1
  77. package/dist/commands/init/gitignore.js +146 -41
  78. package/dist/commands/init/index.d.ts +2 -0
  79. package/dist/commands/init/index.d.ts.map +1 -1
  80. package/dist/commands/init/index.js +3 -0
  81. package/dist/commands/init/managed-block.d.ts +10 -0
  82. package/dist/commands/init/managed-block.d.ts.map +1 -0
  83. package/dist/commands/init/managed-block.js +40 -0
  84. package/dist/commands/init/tools/index.d.ts +1 -0
  85. package/dist/commands/init/tools/index.d.ts.map +1 -1
  86. package/dist/commands/init/tools/index.js +37 -9
  87. package/dist/commands/init/tools/workflows/install-workflows.js +1 -1
  88. package/dist/commands/local/index.d.ts.map +1 -1
  89. package/dist/commands/local/index.js +5 -1
  90. package/dist/commands/local/sync.d.ts +4 -0
  91. package/dist/commands/local/sync.d.ts.map +1 -1
  92. package/dist/commands/local/sync.js +123 -2
  93. package/dist/commands/pjm/adoption.d.ts +2 -0
  94. package/dist/commands/pjm/adoption.d.ts.map +1 -1
  95. package/dist/commands/pjm/adoption.js +1 -1
  96. package/dist/commands/project/archive/archive-utils.d.ts +24 -0
  97. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  98. package/dist/commands/project/archive/archive-utils.js +349 -22
  99. package/dist/commands/project/archive/index.d.ts.map +1 -1
  100. package/dist/commands/project/archive/index.js +1 -0
  101. package/dist/commands/project/archive/push-runner.d.ts +5 -1
  102. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  103. package/dist/commands/project/archive/push-runner.js +33 -4
  104. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  105. package/dist/commands/project/complete-state/index.js +3 -1
  106. package/dist/commands/project/index.d.ts.map +1 -1
  107. package/dist/commands/project/index.js +12 -0
  108. package/dist/commands/project/links/compute.d.ts +9 -0
  109. package/dist/commands/project/links/compute.d.ts.map +1 -0
  110. package/dist/commands/project/links/compute.js +27 -0
  111. package/dist/commands/project/links/index.d.ts +17 -0
  112. package/dist/commands/project/links/index.d.ts.map +1 -0
  113. package/dist/commands/project/links/index.js +72 -0
  114. package/dist/commands/project/links/refresh.d.ts +26 -0
  115. package/dist/commands/project/links/refresh.d.ts.map +1 -0
  116. package/dist/commands/project/links/refresh.js +92 -0
  117. package/dist/commands/project/links/render.d.ts +24 -0
  118. package/dist/commands/project/links/render.d.ts.map +1 -0
  119. package/dist/commands/project/links/render.js +68 -0
  120. package/dist/commands/project/list.d.ts +5 -0
  121. package/dist/commands/project/list.d.ts.map +1 -1
  122. package/dist/commands/project/list.js +182 -9
  123. package/dist/commands/project/migrate/index.d.ts +22 -0
  124. package/dist/commands/project/migrate/index.d.ts.map +1 -0
  125. package/dist/commands/project/migrate/index.js +79 -0
  126. package/dist/commands/project/new/index.d.ts +2 -0
  127. package/dist/commands/project/new/index.d.ts.map +1 -1
  128. package/dist/commands/project/new/index.js +14 -2
  129. package/dist/commands/project/new/scaffold.d.ts +22 -1
  130. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  131. package/dist/commands/project/new/scaffold.js +287 -69
  132. package/dist/commands/project/open/index.d.ts +16 -0
  133. package/dist/commands/project/open/index.d.ts.map +1 -1
  134. package/dist/commands/project/open/index.js +151 -19
  135. package/dist/commands/project/pause/index.d.ts +8 -0
  136. package/dist/commands/project/pause/index.d.ts.map +1 -1
  137. package/dist/commands/project/pause/index.js +129 -19
  138. package/dist/commands/project/prune/index.d.ts +21 -0
  139. package/dist/commands/project/prune/index.d.ts.map +1 -0
  140. package/dist/commands/project/prune/index.js +95 -0
  141. package/dist/commands/project/pull/index.d.ts +28 -0
  142. package/dist/commands/project/pull/index.d.ts.map +1 -0
  143. package/dist/commands/project/pull/index.js +157 -0
  144. package/dist/commands/project/push/index.d.ts +22 -0
  145. package/dist/commands/project/push/index.d.ts.map +1 -0
  146. package/dist/commands/project/push/index.js +100 -0
  147. package/dist/commands/project/scope/index.d.ts +17 -0
  148. package/dist/commands/project/scope/index.d.ts.map +1 -0
  149. package/dist/commands/project/scope/index.js +107 -0
  150. package/dist/commands/project/split/run.d.ts +4 -0
  151. package/dist/commands/project/split/run.d.ts.map +1 -1
  152. package/dist/commands/project/split/run.js +48 -10
  153. package/dist/commands/project/sync/git.d.ts +18 -0
  154. package/dist/commands/project/sync/git.d.ts.map +1 -0
  155. package/dist/commands/project/sync/git.js +60 -0
  156. package/dist/commands/project/sync/record.d.ts +22 -0
  157. package/dist/commands/project/sync/record.d.ts.map +1 -0
  158. package/dist/commands/project/sync/record.js +133 -0
  159. package/dist/commands/project/sync/ref-sync.d.ts +120 -0
  160. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -0
  161. package/dist/commands/project/sync/ref-sync.js +962 -0
  162. package/dist/commands/project/sync/resolve-target.d.ts +27 -0
  163. package/dist/commands/project/sync/resolve-target.d.ts.map +1 -0
  164. package/dist/commands/project/sync/resolve-target.js +159 -0
  165. package/dist/commands/shared/oat-paths.d.ts +4 -1
  166. package/dist/commands/shared/oat-paths.d.ts.map +1 -1
  167. package/dist/commands/shared/oat-paths.js +2 -2
  168. package/dist/commands/shared/project-scope.d.ts +23 -0
  169. package/dist/commands/shared/project-scope.d.ts.map +1 -0
  170. package/dist/commands/shared/project-scope.js +106 -0
  171. package/dist/commands/tools/migrate/index.js +1 -1
  172. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  173. package/dist/commands/tools/shared/pack-inventory.js +79 -8
  174. package/dist/commands/tools/shared/pack-lifecycle.js +1 -1
  175. package/dist/commands/tools/shared/pack-reconcile.d.ts.map +1 -1
  176. package/dist/commands/tools/shared/pack-reconcile.js +4 -1
  177. package/dist/commands/tools/shared/project-tools-config.d.ts +6 -2
  178. package/dist/commands/tools/shared/project-tools-config.d.ts.map +1 -1
  179. package/dist/commands/tools/shared/project-tools-config.js +7 -5
  180. package/dist/commands/tools/update/index.d.ts.map +1 -1
  181. package/dist/commands/tools/update/index.js +38 -14
  182. package/dist/commands/tools/update/update-tools.d.ts +2 -0
  183. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  184. package/dist/config/oat-config.d.ts +6 -3
  185. package/dist/config/oat-config.d.ts.map +1 -1
  186. package/dist/config/oat-config.js +41 -7
  187. package/dist/config/resolve.d.ts.map +1 -1
  188. package/dist/config/resolve.js +2 -1
  189. package/dist/fs/io.d.ts +2 -1
  190. package/dist/fs/io.d.ts.map +1 -1
  191. package/dist/fs/io.js +5 -2
  192. package/dist/projects/split/finalize.d.ts.map +1 -1
  193. package/dist/projects/split/finalize.js +55 -2
  194. package/dist/projects/split/resume.d.ts +3 -0
  195. package/dist/projects/split/resume.d.ts.map +1 -1
  196. package/dist/projects/split/resume.js +34 -7
  197. package/dist/projects/split/seed-children.d.ts.map +1 -1
  198. package/dist/projects/split/seed-children.js +6 -0
  199. package/dist/projects/split/write-parent.d.ts +7 -0
  200. package/dist/projects/split/write-parent.d.ts.map +1 -1
  201. package/dist/projects/split/write-parent.js +7 -4
  202. package/dist/release/public-package-contract.d.ts.map +1 -1
  203. package/dist/release/public-package-contract.js +1 -0
  204. package/dist/validation/skills.d.ts.map +1 -1
  205. package/dist/validation/skills.js +434 -0
  206. package/package.json +4 -4
  207. package/dist/__tests__/skills/split-flow-fixtures.d.ts +0 -50
  208. package/dist/__tests__/skills/split-flow-fixtures.d.ts.map +0 -1
  209. package/dist/__tests__/skills/split-flow-fixtures.js +0 -161
  210. package/dist/commands/__tests__/helpers.d.ts +0 -12
  211. package/dist/commands/__tests__/helpers.d.ts.map +0 -1
  212. package/dist/commands/__tests__/helpers.js +0 -36
@@ -1,12 +1,18 @@
1
1
  import { readFile as defaultReadFile, writeFile as defaultWriteFile, } from 'node:fs/promises';
2
- import { basename, join } from 'node:path';
2
+ import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
3
  import { buildCommandContext } from '../../../app/command-context.js';
4
+ import { defaultGitRunner } from '../../project/sync/git.js';
5
+ import { readSyncedRecord } from '../../project/sync/record.js';
6
+ import { commitRecordChange as defaultCommitRecordChange, pullSynced as defaultPullSynced, pushSynced as defaultPushSynced, } from '../../project/sync/ref-sync.js';
7
+ import { resolveSyncedTarget, } from '../../project/sync/resolve-target.js';
4
8
  import { getFrontmatterBlock, getFrontmatterField, } from '../../shared/frontmatter.js';
5
9
  import { removeFrontmatterField, replaceFrontmatter, upsertFrontmatterField, } from '../../shared/frontmatter-write.js';
6
10
  import { resolveProjectsRoot } from '../../shared/oat-paths.js';
11
+ import { PROJECT_SCOPES, resolveProjectScope, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
7
12
  import { readGlobalOptions } from '../../shared/shared.utils.js';
8
13
  import { generateStateDashboard as defaultGenerateStateDashboard, } from '../../state/generate.js';
9
14
  import { readOatLocalConfig, setActiveProject, writeOatLocalConfig, } from '../../../config/oat-config.js';
15
+ import { CliError } from '../../../errors/cli-error.js';
10
16
  import { dirExists, fileExists } from '../../../fs/io.js';
11
17
  import { resolveProjectRoot } from '../../../fs/paths.js';
12
18
  import { assertValidProjectStateFilesystemContent } from '../../../validation/project-state.js';
@@ -23,14 +29,133 @@ const DEFAULT_DEPENDENCIES = {
23
29
  writeFile: defaultWriteFile,
24
30
  dirExists,
25
31
  fileExists,
32
+ readSyncedRecord,
33
+ resolveSyncedTarget,
34
+ pullSynced: defaultPullSynced,
35
+ pushSynced: defaultPushSynced,
36
+ commitRecordChange: defaultCommitRecordChange,
37
+ gitRunner: defaultGitRunner,
26
38
  processEnv: process.env,
27
39
  now: () => new Date(),
28
40
  };
41
+ function pointerPath(repoRoot, absolutePath) {
42
+ const relativePath = relative(repoRoot, absolutePath);
43
+ return relativePath === '..' ||
44
+ relativePath.startsWith(`..${sep}`) ||
45
+ isAbsolute(relativePath)
46
+ ? absolutePath
47
+ : relativePath.split(sep).join('/');
48
+ }
49
+ function successfulPull(result) {
50
+ return (result.status === 'created' ||
51
+ result.status === 'updated' ||
52
+ result.status === 'up-to-date');
53
+ }
54
+ async function materializeSyncedProject(repoRoot, target, dependencies) {
55
+ const result = await dependencies.pullSynced(target, dependencies.gitRunner, {
56
+ adopt: target.adopt,
57
+ adoptionRecord: target.adoptionRecord,
58
+ });
59
+ if (!successfulPull(result)) {
60
+ throw new CliError(`Unable to open synced project ${target.slug}: pull ${result.status}.`, 1);
61
+ }
62
+ if (result.pendingRecordPaths?.length) {
63
+ await dependencies.commitRecordChange(repoRoot, result.pendingRecordPaths, `chore(oat): adopt synced project ${target.slug}`, dependencies.gitRunner, { projectRoots: target });
64
+ }
65
+ }
66
+ async function resolveProject(repoRoot, projectsRoot, input, dependencies) {
67
+ const sharedRoot = resolveScopeRoot(repoRoot, projectsRoot, 'shared');
68
+ const explicit = isAbsolute(input) || input.includes('/');
69
+ if (explicit) {
70
+ const fullProjectPath = isAbsolute(input)
71
+ ? resolve(input)
72
+ : resolve(repoRoot, input);
73
+ const scope = resolveProjectScope(fullProjectPath, sharedRoot, repoRoot);
74
+ if (!scope) {
75
+ throw new CliError(`Project path is outside configured scope roots: ${input}`, 1);
76
+ }
77
+ const projectName = basename(fullProjectPath);
78
+ let syncTarget;
79
+ if (scope === 'synced') {
80
+ syncTarget = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, fullProjectPath, {}, { allowMissingCheckout: true });
81
+ if (!(await dependencies.dirExists(fullProjectPath)) ||
82
+ syncTarget.adopt) {
83
+ await materializeSyncedProject(repoRoot, syncTarget, dependencies);
84
+ }
85
+ }
86
+ else if (!(await dependencies.dirExists(fullProjectPath))) {
87
+ throw new CliError(`Project not found: ${input}`, 1);
88
+ }
89
+ return {
90
+ projectName,
91
+ projectPath: pointerPath(repoRoot, fullProjectPath),
92
+ fullProjectPath,
93
+ scope,
94
+ syncTarget,
95
+ };
96
+ }
97
+ const candidates = [];
98
+ for (const scope of PROJECT_SCOPES) {
99
+ const scopeRoot = resolveScopeRoot(repoRoot, projectsRoot, scope);
100
+ const fullProjectPath = join(scopeRoot, input);
101
+ if (await dependencies.dirExists(fullProjectPath)) {
102
+ candidates.push({ scope, fullProjectPath, present: true });
103
+ }
104
+ else if (scope === 'synced' &&
105
+ (await dependencies.readSyncedRecord(syncedRecordPath(scopeRoot, input))) !== null) {
106
+ candidates.push({ scope, fullProjectPath, present: false });
107
+ }
108
+ }
109
+ if (candidates.length > 1) {
110
+ throw new CliError(`Project name "${input}" is ambiguous across scopes: ${candidates
111
+ .map((candidate) => candidate.scope)
112
+ .join(', ')}. Pass an explicit project path.`, 1);
113
+ }
114
+ let candidate = candidates[0];
115
+ let syncTarget;
116
+ if (!candidate) {
117
+ try {
118
+ syncTarget = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, input, {}, { allowMissingCheckout: true });
119
+ candidate = {
120
+ scope: 'synced',
121
+ fullProjectPath: syncTarget.projectPath,
122
+ present: false,
123
+ };
124
+ }
125
+ catch (error) {
126
+ if (error instanceof CliError && error.exitCode === 1) {
127
+ throw new CliError(`Project not found: ${input}`, 1);
128
+ }
129
+ throw error;
130
+ }
131
+ }
132
+ if (candidate.scope === 'synced') {
133
+ syncTarget ??= await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, candidate.fullProjectPath, {}, { allowMissingCheckout: true });
134
+ if (!candidate.present || syncTarget.adopt) {
135
+ await materializeSyncedProject(repoRoot, syncTarget, dependencies);
136
+ }
137
+ }
138
+ return {
139
+ projectName: input,
140
+ projectPath: pointerPath(repoRoot, candidate.fullProjectPath),
141
+ fullProjectPath: candidate.fullProjectPath,
142
+ scope: candidate.scope,
143
+ syncTarget,
144
+ };
145
+ }
146
+ async function publishSyncedTransition(target, action, dependencies) {
147
+ const result = await dependencies.pushSynced(target, dependencies.gitRunner, {
148
+ message: `chore(oat): ${action} synced project ${target.slug}`,
149
+ });
150
+ if (result.status !== 'pushed' && result.status !== 'up-to-date') {
151
+ throw new CliError(`Unable to ${action} synced project ${target.slug}: push ${result.status}; active project pointer was not changed.`, 1);
152
+ }
153
+ }
29
154
  async function maybeResumePausedProject(statePath, projectPath, dependencies) {
30
155
  const stateContent = await dependencies.readFile(statePath, 'utf8');
31
156
  const frontmatter = getFrontmatterBlock(stateContent);
32
157
  if (!frontmatter) {
33
- throw new Error(`state.md is missing frontmatter: ${statePath}`);
158
+ throw new CliError(`state.md is missing frontmatter: ${statePath}`, 1);
34
159
  }
35
160
  if (getFrontmatterField(frontmatter, 'oat_lifecycle') !== 'paused') {
36
161
  return false;
@@ -41,10 +166,15 @@ async function maybeResumePausedProject(statePath, projectPath, dependencies) {
41
166
  nextBlock = upsertFrontmatterField(nextBlock, 'oat_project_state_updated', dependencies.now().toISOString(), true).nextBlock;
42
167
  if (nextBlock !== frontmatter) {
43
168
  const nextContent = replaceFrontmatter(stateContent, nextBlock);
44
- await assertValidProjectStateFilesystemContent(nextContent, {
45
- filePath: statePath,
46
- projectPath,
47
- });
169
+ try {
170
+ await assertValidProjectStateFilesystemContent(nextContent, {
171
+ filePath: statePath,
172
+ projectPath,
173
+ });
174
+ }
175
+ catch (error) {
176
+ throw new CliError(error instanceof Error ? error.message : String(error), 1);
177
+ }
48
178
  await dependencies.writeFile(statePath, nextContent, 'utf8');
49
179
  }
50
180
  return true;
@@ -53,23 +183,25 @@ async function runProjectOpen(projectName, options, context, dependencies) {
53
183
  try {
54
184
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
55
185
  const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
56
- const projectPath = join(projectsRoot, projectName);
57
- const fullProjectPath = join(repoRoot, projectPath);
58
- if (!(await dependencies.dirExists(fullProjectPath))) {
59
- throw new Error(`Project not found: ${projectName}`);
60
- }
186
+ const resolved = await resolveProject(repoRoot, projectsRoot, projectName, dependencies);
187
+ const { projectPath, fullProjectPath } = resolved;
61
188
  const statePath = join(fullProjectPath, 'state.md');
62
189
  if (!(await dependencies.fileExists(statePath))) {
63
- throw new Error(`Project state.md not found: ${statePath}`);
190
+ throw new CliError(`Project state.md not found: ${statePath}`, 1);
64
191
  }
65
192
  const localConfig = await dependencies.readOatLocalConfig(repoRoot);
66
193
  const previousActiveProject = localConfig.activeProject ?? null;
67
- if (previousActiveProject === projectPath) {
194
+ const resumedFromPaused = await maybeResumePausedProject(statePath, fullProjectPath, dependencies);
195
+ if (resumedFromPaused && resolved.syncTarget) {
196
+ await publishSyncedTransition(resolved.syncTarget, 'resume', dependencies);
197
+ }
198
+ if (previousActiveProject === projectPath && !resumedFromPaused) {
68
199
  if (context.json) {
69
200
  context.logger.json({
70
201
  status: 'ok',
71
- projectName,
202
+ projectName: resolved.projectName,
72
203
  projectPath,
204
+ scope: resolved.scope,
73
205
  previousActiveProject,
74
206
  resumedFromPaused: false,
75
207
  reason: options.reason ?? null,
@@ -82,7 +214,6 @@ async function runProjectOpen(projectName, options, context, dependencies) {
82
214
  process.exitCode = 0;
83
215
  return;
84
216
  }
85
- const resumedFromPaused = await maybeResumePausedProject(statePath, fullProjectPath, dependencies);
86
217
  await dependencies.setActiveProject(repoRoot, projectPath);
87
218
  if (localConfig.lastPausedProject === projectPath) {
88
219
  const updatedConfig = await dependencies.readOatLocalConfig(repoRoot);
@@ -95,8 +226,9 @@ async function runProjectOpen(projectName, options, context, dependencies) {
95
226
  if (context.json) {
96
227
  context.logger.json({
97
228
  status: 'ok',
98
- projectName,
229
+ projectName: resolved.projectName,
99
230
  projectPath,
231
+ scope: resolved.scope,
100
232
  previousActiveProject,
101
233
  resumedFromPaused,
102
234
  reason: options.reason ?? null,
@@ -104,10 +236,10 @@ async function runProjectOpen(projectName, options, context, dependencies) {
104
236
  }
105
237
  else {
106
238
  if (previousActiveProject) {
107
- context.logger.info(`Switching from ${basename(previousActiveProject)} to ${projectName}.`);
239
+ context.logger.info(`Switching from ${basename(previousActiveProject)} to ${resolved.projectName}.`);
108
240
  }
109
241
  else {
110
- context.logger.info(`Opened project: ${projectName}`);
242
+ context.logger.info(`Opened project: ${resolved.projectName}`);
111
243
  }
112
244
  if (resumedFromPaused) {
113
245
  context.logger.info('Resumed paused project and cleared pause metadata.');
@@ -126,7 +258,7 @@ async function runProjectOpen(projectName, options, context, dependencies) {
126
258
  else {
127
259
  context.logger.error(message);
128
260
  }
129
- process.exitCode = 1;
261
+ process.exitCode = error instanceof CliError ? error.exitCode : 2;
130
262
  }
131
263
  }
132
264
  export function createProjectOpenCommand(overrides = {}) {
@@ -1,5 +1,8 @@
1
1
  import { readFile as defaultReadFile, writeFile as defaultWriteFile } from 'node:fs/promises';
2
2
  import { buildCommandContext, type CommandContext } from '../../../app/command-context.js';
3
+ import { type GitRunner } from '../../project/sync/git.js';
4
+ import { type PushResult, type SyncTarget } from '../../project/sync/ref-sync.js';
5
+ import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
3
6
  import { type GenerateStateResult } from '../../state/generate.js';
4
7
  import { type OatLocalConfig } from '../../../config/oat-config.js';
5
8
  import { dirExists, fileExists } from '../../../fs/io.js';
@@ -19,6 +22,11 @@ interface ProjectPauseDependencies {
19
22
  writeFile: typeof defaultWriteFile;
20
23
  dirExists: typeof dirExists;
21
24
  fileExists: typeof fileExists;
25
+ resolveSyncedTarget: typeof resolveSyncedTarget;
26
+ pushSynced: (target: SyncTarget, git: GitRunner, options: {
27
+ message?: string;
28
+ }) => Promise<PushResult>;
29
+ gitRunner: GitRunner;
22
30
  processEnv: NodeJS.ProcessEnv;
23
31
  now: () => Date;
24
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pause/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAShF,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AA0ID,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAsBT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pause/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,mBAAmB,EAEpB,MAAM,uCAAuC,CAAC;AAe/C,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1B,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAwTD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAsBT"}
@@ -1,12 +1,17 @@
1
1
  import { readFile as defaultReadFile, writeFile as defaultWriteFile, } from 'node:fs/promises';
2
- import { join } from 'node:path';
2
+ import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
3
  import { buildCommandContext } from '../../../app/command-context.js';
4
+ import { defaultGitRunner } from '../../project/sync/git.js';
5
+ import { pushSynced as defaultPushSynced, } from '../../project/sync/ref-sync.js';
6
+ import { resolveSyncedTarget, } from '../../project/sync/resolve-target.js';
4
7
  import { getFrontmatterBlock } from '../../shared/frontmatter.js';
5
8
  import { removeFrontmatterField, replaceFrontmatter, upsertFrontmatterField, } from '../../shared/frontmatter-write.js';
6
9
  import { resolveProjectsRoot } from '../../shared/oat-paths.js';
10
+ import { PROJECT_SCOPES, resolveProjectScope, resolveScopeRoot, } from '../../shared/project-scope.js';
7
11
  import { readGlobalOptions } from '../../shared/shared.utils.js';
8
12
  import { generateStateDashboard as defaultGenerateStateDashboard, } from '../../state/generate.js';
9
13
  import { clearActiveProject, readOatLocalConfig, } from '../../../config/oat-config.js';
14
+ import { CliError } from '../../../errors/cli-error.js';
10
15
  import { dirExists, fileExists } from '../../../fs/io.js';
11
16
  import { resolveProjectRoot } from '../../../fs/paths.js';
12
17
  import { assertValidProjectStateFilesystemContent } from '../../../validation/project-state.js';
@@ -22,40 +27,118 @@ const DEFAULT_DEPENDENCIES = {
22
27
  writeFile: defaultWriteFile,
23
28
  dirExists,
24
29
  fileExists,
30
+ resolveSyncedTarget,
31
+ pushSynced: defaultPushSynced,
32
+ gitRunner: defaultGitRunner,
25
33
  processEnv: process.env,
26
34
  now: () => new Date(),
27
35
  };
36
+ function pointerPath(repoRoot, absolutePath) {
37
+ const relativePath = relative(repoRoot, absolutePath);
38
+ return relativePath === '..' ||
39
+ relativePath.startsWith(`..${sep}`) ||
40
+ isAbsolute(relativePath)
41
+ ? absolutePath
42
+ : relativePath.split(sep).join('/');
43
+ }
44
+ async function resolveNamedProject(repoRoot, projectsRoot, input, dependencies) {
45
+ const sharedRoot = resolveScopeRoot(repoRoot, projectsRoot, 'shared');
46
+ const explicit = isAbsolute(input) || input.includes('/');
47
+ if (explicit) {
48
+ const fullProjectPath = isAbsolute(input)
49
+ ? resolve(input)
50
+ : resolve(repoRoot, input);
51
+ const scope = resolveProjectScope(fullProjectPath, sharedRoot, repoRoot);
52
+ if (!scope) {
53
+ throw new CliError(`Project path is outside configured scope roots: ${input}`, 1);
54
+ }
55
+ if (!(await dependencies.dirExists(fullProjectPath))) {
56
+ throw new CliError(`Project not found: ${input}`, 1);
57
+ }
58
+ return {
59
+ projectName: basename(fullProjectPath),
60
+ projectPath: pointerPath(repoRoot, fullProjectPath),
61
+ fullProjectPath,
62
+ scope,
63
+ syncTarget: scope === 'synced'
64
+ ? await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, fullProjectPath)
65
+ : undefined,
66
+ };
67
+ }
68
+ const candidates = [];
69
+ for (const scope of PROJECT_SCOPES) {
70
+ const fullProjectPath = join(resolveScopeRoot(repoRoot, projectsRoot, scope), input);
71
+ if (!(await dependencies.dirExists(fullProjectPath)))
72
+ continue;
73
+ candidates.push({
74
+ projectName: input,
75
+ projectPath: pointerPath(repoRoot, fullProjectPath),
76
+ fullProjectPath,
77
+ scope,
78
+ syncTarget: scope === 'synced'
79
+ ? await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, fullProjectPath)
80
+ : undefined,
81
+ });
82
+ }
83
+ if (candidates.length > 1) {
84
+ throw new CliError(`Project name "${input}" is ambiguous across scopes: ${candidates
85
+ .map((candidate) => candidate.scope)
86
+ .join(', ')}. Pass an explicit project path.`, 1);
87
+ }
88
+ if (!candidates[0]) {
89
+ throw new CliError(`Project not found: ${input}`, 1);
90
+ }
91
+ return candidates[0];
92
+ }
93
+ async function publishPausedState(target, dependencies) {
94
+ const result = await dependencies.pushSynced(target, dependencies.gitRunner, {
95
+ message: `chore(oat): pause synced project ${target.slug}`,
96
+ });
97
+ if (result.status !== 'pushed' && result.status !== 'up-to-date') {
98
+ const recovery = result.status === 'conflict'
99
+ ? ` Resolve the conflicted files without discarding either side, then run oat project pull ${target.slug} --continue followed by oat project push ${target.slug}, then re-run oat project pause ${target.slug} to finish and clear the active pointer; or run oat project pull ${target.slug} --abort to leave the committed local pause unpublished.`
100
+ : ` The committed local pause was retained with a clean checkout; re-run oat project pause ${target.slug} to retry publication.`;
101
+ throw new PausePublicationError(`Unable to pause synced project ${target.slug}: push ${result.status}; active project pointer was not changed.${recovery}`, result.status);
102
+ }
103
+ return result;
104
+ }
105
+ class PausePublicationError extends CliError {
106
+ status;
107
+ constructor(message, status) {
108
+ super(message, 1);
109
+ this.status = status;
110
+ }
111
+ }
28
112
  async function runProjectPause(projectName, options, context, dependencies) {
29
113
  try {
30
114
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
31
115
  const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
32
116
  const localConfig = await dependencies.readOatLocalConfig(repoRoot);
33
117
  const activeProject = localConfig.activeProject ?? null;
34
- let projectPath;
35
- let resolvedProjectName;
118
+ let resolved;
36
119
  if (projectName) {
37
- projectPath = join(projectsRoot, projectName);
38
- resolvedProjectName = projectName;
120
+ resolved = await resolveNamedProject(repoRoot, projectsRoot, projectName, dependencies);
39
121
  }
40
122
  else {
41
123
  if (!activeProject) {
42
- throw new Error('No project specified and no active project');
124
+ throw new CliError('No project specified and no active project', 1);
43
125
  }
44
- projectPath = activeProject;
45
- resolvedProjectName = projectPath.split('/').at(-1) ?? projectPath;
46
- }
47
- const fullProjectPath = join(repoRoot, projectPath);
48
- if (!(await dependencies.dirExists(fullProjectPath))) {
49
- throw new Error(`Project not found: ${resolvedProjectName}`);
126
+ resolved = await resolveNamedProject(repoRoot, projectsRoot, activeProject, dependencies);
50
127
  }
128
+ const { projectName: resolvedProjectName, projectPath, fullProjectPath, } = resolved;
51
129
  const statePath = join(fullProjectPath, 'state.md');
52
130
  if (!(await dependencies.fileExists(statePath))) {
53
- throw new Error(`Project state.md not found: ${statePath}`);
131
+ throw new CliError(`Project state.md not found: ${statePath}`, 1);
54
132
  }
55
133
  const content = await dependencies.readFile(statePath, 'utf8');
134
+ const prePauseHead = resolved.syncTarget
135
+ ? (await dependencies.gitRunner.run(['rev-parse', 'HEAD'], {
136
+ cwd: resolved.syncTarget.projectPath,
137
+ })).stdout
138
+ : null;
56
139
  const frontmatter = getFrontmatterBlock(content);
57
140
  if (!frontmatter) {
58
- throw new Error(`state.md is missing frontmatter: ${statePath}`);
141
+ throw new CliError(`state.md is missing frontmatter: ${statePath}`, 1);
59
142
  }
60
143
  let nextBlock = upsertFrontmatterField(frontmatter, 'oat_lifecycle', 'paused', true).nextBlock;
61
144
  const nowIso = dependencies.now().toISOString();
@@ -69,12 +152,38 @@ async function runProjectPause(projectName, options, context, dependencies) {
69
152
  }
70
153
  if (nextBlock !== frontmatter) {
71
154
  const nextContent = replaceFrontmatter(content, nextBlock);
72
- await assertValidProjectStateFilesystemContent(nextContent, {
73
- filePath: statePath,
74
- projectPath: fullProjectPath,
75
- });
155
+ try {
156
+ await assertValidProjectStateFilesystemContent(nextContent, {
157
+ filePath: statePath,
158
+ projectPath: fullProjectPath,
159
+ });
160
+ }
161
+ catch (error) {
162
+ throw new CliError(error instanceof Error ? error.message : String(error), 1);
163
+ }
76
164
  await dependencies.writeFile(statePath, nextContent, 'utf8');
77
165
  }
166
+ if (resolved.syncTarget) {
167
+ try {
168
+ await publishPausedState(resolved.syncTarget, dependencies);
169
+ }
170
+ catch (error) {
171
+ if (nextBlock !== frontmatter &&
172
+ !(error instanceof PausePublicationError)) {
173
+ const pauseCommitRetained = resolved.syncTarget && prePauseHead
174
+ ? (await dependencies.gitRunner.run(['rev-parse', 'HEAD'], {
175
+ cwd: resolved.syncTarget.projectPath,
176
+ })).stdout !== prePauseHead
177
+ : false;
178
+ if (pauseCommitRetained && resolved.syncTarget) {
179
+ const detail = error instanceof Error ? error.message : String(error);
180
+ throw new PausePublicationError(`Unable to pause synced project ${resolved.syncTarget.slug}: publication failed: ${detail}. Active project pointer was not changed. The committed local pause was retained with a clean checkout; re-run oat project pause ${resolved.syncTarget.slug} to retry publication.`, 'failed');
181
+ }
182
+ await dependencies.writeFile(statePath, content, 'utf8');
183
+ }
184
+ throw error;
185
+ }
186
+ }
78
187
  const pointerCleared = activeProject === projectPath;
79
188
  if (pointerCleared) {
80
189
  await dependencies.clearActiveProject(repoRoot, {
@@ -87,6 +196,7 @@ async function runProjectPause(projectName, options, context, dependencies) {
87
196
  status: 'ok',
88
197
  projectName: resolvedProjectName,
89
198
  projectPath,
199
+ scope: resolved.scope,
90
200
  pointerCleared,
91
201
  reason: options.reason ?? null,
92
202
  });
@@ -107,7 +217,7 @@ async function runProjectPause(projectName, options, context, dependencies) {
107
217
  else {
108
218
  context.logger.error(message);
109
219
  }
110
- process.exitCode = 1;
220
+ process.exitCode = error instanceof CliError ? error.exitCode : 2;
111
221
  }
112
222
  }
113
223
  export function createProjectPauseCommand(overrides = {}) {
@@ -0,0 +1,21 @@
1
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
+ import { type GitRunner } from '../../project/sync/git.js';
3
+ import { type PruneResult, type SyncTarget } from '../../project/sync/ref-sync.js';
4
+ import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
5
+ import { Command } from 'commander';
6
+ interface ProjectPruneDependencies {
7
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
8
+ resolveProjectRoot: (cwd: string) => Promise<string>;
9
+ resolveSyncedTarget: typeof resolveSyncedTarget;
10
+ pruneSynced: (target: SyncTarget, git: GitRunner, options: {
11
+ force: boolean;
12
+ commit: boolean;
13
+ }) => Promise<PruneResult>;
14
+ readProjectState: (target: SyncTarget, git: GitRunner) => Promise<string | null>;
15
+ gitRunner: GitRunner;
16
+ processEnv: NodeJS.ProcessEnv;
17
+ }
18
+ export declare function readPruneProjectState(target: SyncTarget, git: GitRunner): Promise<string | null>;
19
+ export declare function createProjectPruneCommand(overrides?: Partial<ProjectPruneDependencies>): Command;
20
+ export {};
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/prune/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE9E,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAS5E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,WAAW,EAAE,CACX,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KACzC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1B,gBAAgB,EAAE,CAChB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,KACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BxB;AAmED,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAmBT"}
@@ -0,0 +1,95 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { buildCommandContext, } from '../../../app/command-context.js';
4
+ import { defaultGitRunner } from '../../project/sync/git.js';
5
+ import { readSyncedRecord } from '../../project/sync/record.js';
6
+ import { pruneSynced, } from '../../project/sync/ref-sync.js';
7
+ import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
8
+ import { getFrontmatterBlock, parseFrontmatterScalarFields, } from '../../shared/frontmatter.js';
9
+ import { syncedRecordPath } from '../../shared/project-scope.js';
10
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
11
+ import { CliError } from '../../../errors/cli-error.js';
12
+ import { resolveProjectRoot } from '../../../fs/paths.js';
13
+ import { Command } from 'commander';
14
+ export async function readPruneProjectState(target, git) {
15
+ try {
16
+ return await readFile(join(target.projectPath, 'state.md'), 'utf8');
17
+ }
18
+ catch {
19
+ // A completed or not-yet-pulled project may have no checkout.
20
+ }
21
+ const fetched = await git.run(['fetch', target.remote, `+${target.ref}:${target.ref}`], { cwd: target.repoRoot, allowFailure: true });
22
+ if (fetched.code === 0) {
23
+ const shown = await git.run(['show', `${target.ref}:state.md`], {
24
+ cwd: target.repoRoot,
25
+ allowFailure: true,
26
+ });
27
+ if (shown.code === 0)
28
+ return shown.stdout;
29
+ }
30
+ const record = await readSyncedRecord(syncedRecordPath(target.syncedRoot, target.slug));
31
+ const snapshot = record?.archiveSnapshot ?? target.slug;
32
+ try {
33
+ return await readFile(join(target.syncedRoot, '..', 'archived', snapshot, 'state.md'), 'utf8');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ const DEFAULT_DEPENDENCIES = {
40
+ buildCommandContext,
41
+ resolveProjectRoot,
42
+ resolveSyncedTarget,
43
+ pruneSynced,
44
+ readProjectState: readPruneProjectState,
45
+ gitRunner: defaultGitRunner,
46
+ processEnv: process.env,
47
+ };
48
+ function hasOpenPr(state) {
49
+ const frontmatter = state ? getFrontmatterBlock(state) : null;
50
+ if (!frontmatter)
51
+ return false;
52
+ const parsed = parseFrontmatterScalarFields(frontmatter, ['oat_pr_status']);
53
+ return parsed.valid && parsed.values.oat_pr_status === 'open';
54
+ }
55
+ async function runPrune(context, pathOrSlug, options, dependencies) {
56
+ try {
57
+ const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
58
+ const target = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, pathOrSlug, { gitRunner: dependencies.gitRunner }, {
59
+ allowMissingCheckout: true,
60
+ allowStagedPruneDeletion: true,
61
+ });
62
+ const state = await dependencies.readProjectState(target, dependencies.gitRunner);
63
+ if (!options.force && hasOpenPr(state)) {
64
+ throw new CliError(`Refusing to prune ${target.slug} while its PR is open. Pass --force to confirm; pinned links will stop resolving.`, 1);
65
+ }
66
+ context.logger.warn(`Pruning ${target.slug} removes ${target.ref}; pinned links will stop resolving.`);
67
+ const result = await dependencies.pruneSynced(target, dependencies.gitRunner, { force: options.force, commit: options.commit });
68
+ const payload = { ...result, ref: target.ref };
69
+ if (context.json)
70
+ context.logger.json(payload);
71
+ else
72
+ context.logger.info(`Pruned synced project ${target.slug}.`);
73
+ process.exitCode = 0;
74
+ }
75
+ catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ if (context.json)
78
+ context.logger.json({ status: 'error', message });
79
+ else
80
+ context.logger.error(message);
81
+ process.exitCode = error instanceof CliError ? error.exitCode : 2;
82
+ }
83
+ }
84
+ export function createProjectPruneCommand(overrides = {}) {
85
+ const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
86
+ return new Command('prune')
87
+ .description('Permanently remove a synced project and its ref')
88
+ .argument('[project-path|slug]', 'Synced project path or slug')
89
+ .option('--force', 'Prune despite open PR or unsafe checkout state', false)
90
+ .option('--no-commit', 'Do not commit the discovery record deletion')
91
+ .action(async (pathOrSlug, options, command) => {
92
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
93
+ await runPrune(context, pathOrSlug, options, dependencies);
94
+ });
95
+ }
@@ -0,0 +1,28 @@
1
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
+ import { type GitRunner } from '../../project/sync/git.js';
3
+ import { commitRecordChange as defaultCommitRecordChange, type PullChildResult, type PullResult, type AdoptionRecordState, type SyncTarget } from '../../project/sync/ref-sync.js';
4
+ import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
5
+ import { Command } from 'commander';
6
+ interface ProjectPullDependencies {
7
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
8
+ resolveProjectRoot: (cwd: string) => Promise<string>;
9
+ resolveSyncedTarget: typeof resolveSyncedTarget;
10
+ pullSynced: (target: SyncTarget, git: GitRunner, options?: {
11
+ adopt?: boolean;
12
+ adoptionRecord?: AdoptionRecordState;
13
+ now?: Date;
14
+ }) => Promise<PullResult>;
15
+ pullChildren: (target: SyncTarget, git: GitRunner) => Promise<PullChildResult[]>;
16
+ commitRecordChange: typeof defaultCommitRecordChange;
17
+ continueSynced: (target: SyncTarget, git: GitRunner, options?: {
18
+ adopt?: boolean;
19
+ adoptionRecord?: AdoptionRecordState;
20
+ now?: Date;
21
+ }) => Promise<PullResult>;
22
+ abortSynced: (target: SyncTarget, git: GitRunner) => Promise<void>;
23
+ gitRunner: GitRunner;
24
+ processEnv: NodeJS.ProcessEnv;
25
+ }
26
+ export declare function createProjectPullCommand(overrides?: Partial<ProjectPullDependencies>): Command;
27
+ export {};
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pull/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAEL,kBAAkB,IAAI,yBAAyB,EAI/C,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,mBAAmB,EAEpB,MAAM,uCAAuC,CAAC;AAI/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,mBAAmB,CAAC;QACrC,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,KACE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,YAAY,EAAE,CACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,KACX,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAChC,kBAAkB,EAAE,OAAO,yBAAyB,CAAC;IACrD,cAAc,EAAE,CACd,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,mBAAmB,CAAC;QACrC,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,KACE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAiLD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAqBT"}