@aipper/aiws 0.0.29 → 0.0.31

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 (278) hide show
  1. package/bin/aiws.js +2 -2
  2. package/dist/aiws-package.d.ts +4 -0
  3. package/dist/aiws-package.js +11 -0
  4. package/dist/aiws-package.js.map +1 -0
  5. package/dist/aiws-paths.d.ts +48 -0
  6. package/dist/aiws-paths.js +129 -0
  7. package/dist/aiws-paths.js.map +1 -0
  8. package/dist/backup.d.ts +27 -0
  9. package/dist/backup.js +125 -0
  10. package/dist/backup.js.map +1 -0
  11. package/dist/cli.d.ts +1 -0
  12. package/dist/cli.js +773 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/codex-prompts.d.ts +17 -0
  15. package/dist/codex-prompts.js +49 -0
  16. package/dist/codex-prompts.js.map +1 -0
  17. package/dist/codex-skills.d.ts +17 -0
  18. package/dist/codex-skills.js +75 -0
  19. package/dist/codex-skills.js.map +1 -0
  20. package/dist/commands/change-advice.d.ts +25 -0
  21. package/dist/commands/change-advice.js +197 -0
  22. package/dist/commands/change-advice.js.map +1 -0
  23. package/dist/commands/change-evidence-command.d.ts +29 -0
  24. package/dist/commands/change-evidence-command.js +213 -0
  25. package/dist/commands/change-evidence-command.js.map +1 -0
  26. package/dist/commands/change-evidence-entry.d.ts +16 -0
  27. package/dist/commands/change-evidence-entry.js +35 -0
  28. package/dist/commands/change-evidence-entry.js.map +1 -0
  29. package/dist/commands/change-evidence.d.ts +108 -0
  30. package/dist/commands/change-evidence.js +341 -0
  31. package/dist/commands/change-evidence.js.map +1 -0
  32. package/dist/commands/change-finish.d.ts +71 -0
  33. package/dist/commands/change-finish.js +387 -0
  34. package/dist/commands/change-finish.js.map +1 -0
  35. package/dist/commands/change-git-status.d.ts +22 -0
  36. package/dist/commands/change-git-status.js +50 -0
  37. package/dist/commands/change-git-status.js.map +1 -0
  38. package/dist/commands/change-lifecycle-entry.d.ts +29 -0
  39. package/{src → dist}/commands/change-lifecycle-entry.js +21 -28
  40. package/dist/commands/change-lifecycle-entry.js.map +1 -0
  41. package/dist/commands/change-lifecycle.d.ts +56 -0
  42. package/dist/commands/change-lifecycle.js +211 -0
  43. package/dist/commands/change-lifecycle.js.map +1 -0
  44. package/dist/commands/change-metrics-command.d.ts +12 -0
  45. package/dist/commands/change-metrics-command.js +110 -0
  46. package/dist/commands/change-metrics-command.js.map +1 -0
  47. package/dist/commands/change-next-command.d.ts +18 -0
  48. package/dist/commands/change-next-command.js +138 -0
  49. package/dist/commands/change-next-command.js.map +1 -0
  50. package/dist/commands/change-review-gates.d.ts +107 -0
  51. package/dist/commands/change-review-gates.js +329 -0
  52. package/dist/commands/change-review-gates.js.map +1 -0
  53. package/dist/commands/change-scope-gate.d.ts +21 -0
  54. package/dist/commands/change-scope-gate.js +32 -0
  55. package/dist/commands/change-scope-gate.js.map +1 -0
  56. package/dist/commands/change-start.d.ts +30 -0
  57. package/dist/commands/change-start.js +91 -0
  58. package/dist/commands/change-start.js.map +1 -0
  59. package/dist/commands/change-state-command.d.ts +3 -0
  60. package/dist/commands/change-state-command.js +32 -0
  61. package/dist/commands/change-state-command.js.map +1 -0
  62. package/dist/commands/change-status-command.d.ts +13 -0
  63. package/dist/commands/change-status-command.js +126 -0
  64. package/dist/commands/change-status-command.js.map +1 -0
  65. package/dist/commands/change-status-context.d.ts +58 -0
  66. package/dist/commands/change-status-context.js +166 -0
  67. package/dist/commands/change-status-context.js.map +1 -0
  68. package/dist/commands/change-task-stats.d.ts +24 -0
  69. package/dist/commands/change-task-stats.js +66 -0
  70. package/dist/commands/change-task-stats.js.map +1 -0
  71. package/dist/commands/change-validate-entry.d.ts +17 -0
  72. package/dist/commands/change-validate-entry.js +44 -0
  73. package/dist/commands/change-validate-entry.js.map +1 -0
  74. package/dist/commands/change-validate.d.ts +32 -0
  75. package/dist/commands/change-validate.js +128 -0
  76. package/dist/commands/change-validate.js.map +1 -0
  77. package/dist/commands/change.d.ts +322 -0
  78. package/dist/commands/change.js +1731 -0
  79. package/dist/commands/change.js.map +1 -0
  80. package/dist/commands/codex-install-prompts.d.ts +4 -0
  81. package/dist/commands/codex-install-prompts.js +68 -0
  82. package/dist/commands/codex-install-prompts.js.map +1 -0
  83. package/dist/commands/codex-install-skills.d.ts +4 -0
  84. package/dist/commands/codex-install-skills.js +74 -0
  85. package/dist/commands/codex-install-skills.js.map +1 -0
  86. package/dist/commands/codex-status-prompts.d.ts +4 -0
  87. package/dist/commands/codex-status-prompts.js +44 -0
  88. package/dist/commands/codex-status-prompts.js.map +1 -0
  89. package/dist/commands/codex-status-skills.d.ts +4 -0
  90. package/dist/commands/codex-status-skills.js +54 -0
  91. package/dist/commands/codex-status-skills.js.map +1 -0
  92. package/dist/commands/codex-uninstall-prompts.d.ts +4 -0
  93. package/dist/commands/codex-uninstall-prompts.js +51 -0
  94. package/dist/commands/codex-uninstall-prompts.js.map +1 -0
  95. package/dist/commands/codex-uninstall-skills.d.ts +4 -0
  96. package/dist/commands/codex-uninstall-skills.js +59 -0
  97. package/dist/commands/codex-uninstall-skills.js.map +1 -0
  98. package/dist/commands/dashboard.d.ts +222 -0
  99. package/dist/commands/dashboard.js +193 -0
  100. package/dist/commands/dashboard.js.map +1 -0
  101. package/dist/commands/hooks-install.d.ts +4 -0
  102. package/dist/commands/hooks-install.js +90 -0
  103. package/dist/commands/hooks-install.js.map +1 -0
  104. package/dist/commands/hooks-status.d.ts +4 -0
  105. package/dist/commands/hooks-status.js +89 -0
  106. package/dist/commands/hooks-status.js.map +1 -0
  107. package/dist/commands/init.d.ts +4 -0
  108. package/dist/commands/init.js +100 -0
  109. package/dist/commands/init.js.map +1 -0
  110. package/dist/commands/opencode-auto.d.ts +11 -0
  111. package/dist/commands/opencode-auto.js +158 -0
  112. package/dist/commands/opencode-auto.js.map +1 -0
  113. package/dist/commands/opencode-status.d.ts +4 -0
  114. package/dist/commands/opencode-status.js +75 -0
  115. package/dist/commands/opencode-status.js.map +1 -0
  116. package/dist/commands/opencode-supervise.d.ts +24 -0
  117. package/dist/commands/opencode-supervise.js +188 -0
  118. package/dist/commands/opencode-supervise.js.map +1 -0
  119. package/dist/commands/rollback.d.ts +4 -0
  120. package/dist/commands/rollback.js +12 -0
  121. package/dist/commands/rollback.js.map +1 -0
  122. package/dist/commands/update.d.ts +4 -0
  123. package/dist/commands/update.js +212 -0
  124. package/dist/commands/update.js.map +1 -0
  125. package/dist/commands/validate.d.ts +4 -0
  126. package/dist/commands/validate.js +222 -0
  127. package/dist/commands/validate.js.map +1 -0
  128. package/dist/commands/ws-commit.d.ts +5 -0
  129. package/dist/commands/ws-commit.js +39 -0
  130. package/dist/commands/ws-commit.js.map +1 -0
  131. package/dist/commands/ws-deliver.d.ts +5 -0
  132. package/dist/commands/ws-deliver.js +59 -0
  133. package/dist/commands/ws-deliver.js.map +1 -0
  134. package/dist/commands/ws-finish.d.ts +5 -0
  135. package/dist/commands/ws-finish.js +142 -0
  136. package/dist/commands/ws-finish.js.map +1 -0
  137. package/dist/commands/ws-handoff.d.ts +5 -0
  138. package/dist/commands/ws-handoff.js +35 -0
  139. package/dist/commands/ws-handoff.js.map +1 -0
  140. package/dist/commands/ws-migrate.d.ts +5 -0
  141. package/dist/commands/ws-migrate.js +41 -0
  142. package/dist/commands/ws-migrate.js.map +1 -0
  143. package/dist/commands/ws-plan-verify.d.ts +5 -0
  144. package/dist/commands/ws-plan-verify.js +38 -0
  145. package/dist/commands/ws-plan-verify.js.map +1 -0
  146. package/dist/commands/ws-pull.d.ts +5 -0
  147. package/dist/commands/ws-pull.js +26 -0
  148. package/dist/commands/ws-pull.js.map +1 -0
  149. package/dist/commands/ws-push.d.ts +5 -0
  150. package/dist/commands/ws-push.js +68 -0
  151. package/dist/commands/ws-push.js.map +1 -0
  152. package/dist/commands/ws-submodule-setup.d.ts +5 -0
  153. package/dist/commands/ws-submodule-setup.js +55 -0
  154. package/dist/commands/ws-submodule-setup.js.map +1 -0
  155. package/dist/commands/ws-verify-before-complete.d.ts +5 -0
  156. package/dist/commands/ws-verify-before-complete.js +35 -0
  157. package/dist/commands/ws-verify-before-complete.js.map +1 -0
  158. package/dist/dashboard/app.d.ts +1 -0
  159. package/dist/dashboard/app.js +370 -0
  160. package/dist/dashboard/app.js.map +1 -0
  161. package/dist/delegation/cli.d.ts +6 -0
  162. package/dist/delegation/cli.js +14 -0
  163. package/dist/delegation/cli.js.map +1 -0
  164. package/dist/delegation/index.d.ts +35 -0
  165. package/dist/delegation/index.js +118 -0
  166. package/dist/delegation/index.js.map +1 -0
  167. package/dist/errors.d.ts +8 -0
  168. package/dist/errors.js +11 -0
  169. package/dist/errors.js.map +1 -0
  170. package/dist/exec.d.ts +9 -0
  171. package/dist/exec.js +22 -0
  172. package/dist/exec.js.map +1 -0
  173. package/dist/fs.d.ts +9 -0
  174. package/dist/fs.js +62 -0
  175. package/dist/fs.js.map +1 -0
  176. package/dist/governance.d.ts +10 -0
  177. package/dist/governance.js +135 -0
  178. package/dist/governance.js.map +1 -0
  179. package/dist/hash.d.ts +3 -0
  180. package/dist/hash.js +13 -0
  181. package/dist/hash.js.map +1 -0
  182. package/dist/hooks/deploy-claude.d.ts +13 -0
  183. package/dist/hooks/deploy-claude.js +67 -0
  184. package/dist/hooks/deploy-claude.js.map +1 -0
  185. package/dist/journal.d.ts +22 -0
  186. package/dist/journal.js +112 -0
  187. package/dist/journal.js.map +1 -0
  188. package/dist/json-schema-lite.d.ts +22 -0
  189. package/dist/json-schema-lite.js +148 -0
  190. package/dist/json-schema-lite.js.map +1 -0
  191. package/dist/managed-blocks.d.ts +24 -0
  192. package/dist/managed-blocks.js +84 -0
  193. package/dist/managed-blocks.js.map +1 -0
  194. package/dist/manifest.d.ts +24 -0
  195. package/dist/manifest.js +145 -0
  196. package/dist/manifest.js.map +1 -0
  197. package/dist/opencode-env.d.ts +62 -0
  198. package/dist/opencode-env.js +178 -0
  199. package/dist/opencode-env.js.map +1 -0
  200. package/dist/path-utils.d.ts +2 -0
  201. package/dist/path-utils.js +8 -0
  202. package/dist/path-utils.js.map +1 -0
  203. package/dist/spec.d.ts +17 -0
  204. package/dist/spec.js +174 -0
  205. package/dist/spec.js.map +1 -0
  206. package/dist/template.d.ts +20 -0
  207. package/dist/template.js +123 -0
  208. package/dist/template.js.map +1 -0
  209. package/dist/workspace.d.ts +4 -0
  210. package/dist/workspace.js +19 -0
  211. package/dist/workspace.js.map +1 -0
  212. package/package.json +11 -3
  213. package/src/aiws-package.js +0 -15
  214. package/src/backup.js +0 -149
  215. package/src/cli.js +0 -760
  216. package/src/codex-prompts.js +0 -74
  217. package/src/codex-skills.js +0 -111
  218. package/src/commands/change-advice.js +0 -181
  219. package/src/commands/change-evidence-command.js +0 -242
  220. package/src/commands/change-evidence-entry.js +0 -40
  221. package/src/commands/change-evidence.js +0 -340
  222. package/src/commands/change-finish.js +0 -413
  223. package/src/commands/change-git-status.js +0 -53
  224. package/src/commands/change-lifecycle.js +0 -236
  225. package/src/commands/change-metrics-command.js +0 -110
  226. package/src/commands/change-next-command.js +0 -133
  227. package/src/commands/change-review-gates.js +0 -331
  228. package/src/commands/change-scope-gate.js +0 -34
  229. package/src/commands/change-start.js +0 -85
  230. package/src/commands/change-state-command.js +0 -40
  231. package/src/commands/change-status-command.js +0 -125
  232. package/src/commands/change-status-context.js +0 -173
  233. package/src/commands/change-task-stats.js +0 -64
  234. package/src/commands/change-validate-entry.js +0 -45
  235. package/src/commands/change-validate.js +0 -127
  236. package/src/commands/change.js +0 -1829
  237. package/src/commands/codex-install-prompts.js +0 -68
  238. package/src/commands/codex-install-skills.js +0 -74
  239. package/src/commands/codex-status-prompts.js +0 -55
  240. package/src/commands/codex-status-skills.js +0 -63
  241. package/src/commands/codex-uninstall-prompts.js +0 -55
  242. package/src/commands/codex-uninstall-skills.js +0 -62
  243. package/src/commands/dashboard.js +0 -205
  244. package/src/commands/hooks-install.js +0 -92
  245. package/src/commands/hooks-status.js +0 -95
  246. package/src/commands/init.js +0 -101
  247. package/src/commands/opencode-auto.js +0 -167
  248. package/src/commands/opencode-status.js +0 -103
  249. package/src/commands/opencode-supervise.js +0 -200
  250. package/src/commands/rollback.js +0 -13
  251. package/src/commands/update.js +0 -145
  252. package/src/commands/validate.js +0 -228
  253. package/src/commands/ws-commit.js +0 -44
  254. package/src/commands/ws-deliver.js +0 -67
  255. package/src/commands/ws-finish.js +0 -83
  256. package/src/commands/ws-handoff.js +0 -40
  257. package/src/commands/ws-migrate.js +0 -41
  258. package/src/commands/ws-plan-verify.js +0 -44
  259. package/src/commands/ws-pull.js +0 -30
  260. package/src/commands/ws-push.js +0 -71
  261. package/src/commands/ws-submodule-setup.js +0 -65
  262. package/src/commands/ws-verify-before-complete.js +0 -39
  263. package/src/dashboard/app.js +0 -371
  264. package/src/errors.js +0 -15
  265. package/src/exec.js +0 -34
  266. package/src/fs.js +0 -91
  267. package/src/governance.js +0 -159
  268. package/src/hash.js +0 -25
  269. package/src/json-schema-lite.js +0 -164
  270. package/src/managed-blocks.js +0 -131
  271. package/src/manifest.js +0 -177
  272. package/src/opencode-env.js +0 -208
  273. package/src/path-utils.js +0 -20
  274. package/src/spec.js +0 -195
  275. package/src/template.js +0 -187
  276. package/src/workspace.js +0 -23
  277. /package/{src → dist}/dashboard/index.html +0 -0
  278. /package/{src → dist}/dashboard/style.css +0 -0
@@ -1,145 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { loadTemplate } from "../spec.js";
4
- import { loadAiwsPackage } from "../aiws-package.js";
5
- import { resolveWorkspaceRoot } from "../workspace.js";
6
- import { readText, pathExists } from "../fs.js";
7
- import { UserError } from "../errors.js";
8
- import { normalizeNewlines } from "../hash.js";
9
- import { findManagedBlock } from "../managed-blocks.js";
10
- import { BackupSession } from "../backup.js";
11
- import { normalizeRel, joinRel } from "../path-utils.js";
12
- import { copyTemplateFileToWorkspace, applyManagedBlocksFromTemplate } from "../template.js";
13
- import { detectLegacyWorkspaceTemplateId, writeWorkspaceManifest } from "../manifest.js";
14
-
15
- /**
16
- * @param {{ targetPath: string }} options
17
- */
18
- export async function updateCommand(options) {
19
- const workspaceRoot = await resolveWorkspaceRoot(options.targetPath, { create: false });
20
- const aiws = await loadAiwsPackage();
21
-
22
- const manifestPath = path.join(workspaceRoot, ".aiws", "manifest.json");
23
- const hasManifest = await pathExists(manifestPath);
24
- const legacyTemplateId = !hasManifest ? await detectLegacyWorkspaceTemplateId(workspaceRoot) : null;
25
- const legacyMigration = !hasManifest && Boolean(legacyTemplateId);
26
- if (!hasManifest && !legacyTemplateId) {
27
- throw new UserError("Missing .aiws/manifest.json. Run `aiws init` first.");
28
- }
29
-
30
- const stored = hasManifest
31
- ? JSON.parse(await readText(manifestPath))
32
- : { template_id: legacyTemplateId, installed_at: "", tools: [], managed: [] };
33
- const templateId = String(stored.template_id || legacyTemplateId || "workspace");
34
- const tpl = await loadTemplate(templateId);
35
- const storedManagedByPath = new Map(
36
- (Array.isArray(stored.managed) ? stored.managed : [])
37
- .filter((entry) => entry && typeof entry === "object")
38
- .map((entry) => [normalizeRel(String(entry.path || "")), entry]),
39
- );
40
- const defaults = tpl.manifest.defaults || {};
41
- const defaultTools = Array.isArray(defaults.tools) ? defaults.tools.map(String) : ["claude", "opencode", "codex"];
42
- const allowedTools = new Set(defaultTools);
43
-
44
- const update = tpl.manifest.update || {};
45
- const replaceFiles = (update.replace_file || []).map(normalizeRel);
46
- const managedBlocks = update.managed_blocks && typeof update.managed_blocks === "object" ? update.managed_blocks : {};
47
-
48
- // Preflight: existing managed-block files must be intact.
49
- // Missing files that were not previously managed are treated as newly introduced
50
- // template surfaces and will be created during applyManagedBlocksFromTemplate.
51
- for (const [fileRelRaw, blockIdsRaw] of Object.entries(managedBlocks)) {
52
- const fileRel = normalizeRel(fileRelRaw);
53
- const abs = joinRel(workspaceRoot, fileRel);
54
- const hadManagedBefore = storedManagedByPath.has(fileRel);
55
- if (!(await pathExists(abs))) {
56
- if (hadManagedBefore) {
57
- throw new UserError("Managed block file missing; run `aiws init` or restore the file.", { details: `Missing: ${fileRel}` });
58
- }
59
- continue;
60
- }
61
- const text = normalizeNewlines(await readText(abs));
62
- const ids = Array.isArray(blockIdsRaw) ? blockIdsRaw.map(String) : [];
63
- for (const id of ids) {
64
- if (!findManagedBlock(text, id)) {
65
- if (legacyMigration) {
66
- continue;
67
- }
68
- throw new UserError("Managed block markers are missing or broken; refusing to update.", {
69
- details: `File: ${fileRel}\nBlock: ${id}\nHint: re-run \`aiws init\` or repair markers manually.`,
70
- });
71
- }
72
- }
73
- }
74
-
75
- const removeFiles = (update.remove || []).map(normalizeRel);
76
-
77
- const backup = new BackupSession({ workspaceRoot, operation: "update" });
78
- // Backup every file we might touch (including missing, for rollback deletions).
79
- await backup.recordFile(".aiws/manifest.json", { recordMissing: true });
80
- for (const f of replaceFiles) await backup.recordFile(f, { recordMissing: true });
81
- for (const f of Object.keys(managedBlocks)) await backup.recordFile(f, { recordMissing: true });
82
- for (const f of removeFiles) await backup.recordFile(f);
83
- await backup.finalize({ extra: { template_id: tpl.templateId } });
84
-
85
- // Remove deprecated files (backed up above; restorable via `aiws rollback`).
86
- let removedCount = 0;
87
- for (const rel of removeFiles) {
88
- if (!rel) continue;
89
- const abs = joinRel(workspaceRoot, rel);
90
- if (await pathExists(abs)) {
91
- await fs.rm(abs, { force: true });
92
- removedCount++;
93
- // Remove empty parent directory (safe: rmdir fails on non-empty).
94
- try { await fs.rmdir(path.dirname(abs)); } catch { /* non-empty or gone */ }
95
- }
96
- }
97
- if (removedCount > 0) {
98
- console.log(` removed ${removedCount} deprecated file(s)`);
99
- }
100
-
101
- // Replace files (except manifest, generated later).
102
- for (const rel of replaceFiles) {
103
- if (!rel || rel === ".aiws/manifest.json") continue;
104
- await copyTemplateFileToWorkspace({
105
- templateDir: tpl.templateDir,
106
- workspaceRoot,
107
- relPosix: rel,
108
- chmod: rel.startsWith(".githooks/") ? 0o755 : undefined,
109
- });
110
- }
111
-
112
- // Update managed blocks only (do not insert missing blocks).
113
- for (const [fileRelRaw, blockIdsRaw] of Object.entries(managedBlocks)) {
114
- const fileRel = normalizeRel(fileRelRaw);
115
- const ids = Array.isArray(blockIdsRaw) ? blockIdsRaw.map(String) : [];
116
- await applyManagedBlocksFromTemplate({
117
- templateDir: tpl.templateDir,
118
- workspaceRoot,
119
- fileRel,
120
- blockIds: ids,
121
- insertIfMissing: legacyMigration,
122
- });
123
- }
124
-
125
- const now = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
126
- const installedAt = String(stored.installed_at || now);
127
- const storedTools = Array.isArray(stored.tools) ? stored.tools.map(String).filter((tool) => allowedTools.has(tool)) : [];
128
- const tools = storedTools.length > 0 ? storedTools : defaultTools;
129
-
130
- await writeWorkspaceManifest({
131
- workspaceRoot,
132
- templateId: tpl.templateId,
133
- specVersion: tpl.specVersion,
134
- aiwsVersion: aiws.version,
135
- installedAt,
136
- updatedAt: now,
137
- tools,
138
- templateManifest: tpl.manifest,
139
- });
140
-
141
- if (legacyMigration) {
142
- console.log(` migrated legacy AIWS workspace: generated .aiws/manifest.json for template=${legacyTemplateId}`);
143
- }
144
- console.log(`✓ aiws update: ${workspaceRoot}`);
145
- }
@@ -1,228 +0,0 @@
1
- import path from "node:path";
2
- import { loadTemplate } from "../spec.js";
3
- import { resolveWorkspaceRoot } from "../workspace.js";
4
- import { ensureDir, pathExists, readText, writeText } from "../fs.js";
5
- import { UserError } from "../errors.js";
6
- import { detectLegacyWorkspaceTemplateId, validateDrift } from "../manifest.js";
7
- import { runCommand } from "../exec.js";
8
- import { expandManifestEntries } from "../template.js";
9
- import { loadAiwsPackage } from "../aiws-package.js";
10
-
11
- /**
12
- * Enforce deterministic submodule branch policy:
13
- * - If `.gitmodules` exists and declares submodules, each submodule must declare `submodule.<name>.branch`.
14
- * - This avoids guessing which branch to attach/push for workflows like ws-pull/ws-finish.
15
- *
16
- * @param {string} workspaceRoot
17
- */
18
- async function validateSubmoduleBranchPolicy(workspaceRoot) {
19
- const gitmodules = path.join(workspaceRoot, ".gitmodules");
20
- if (!(await pathExists(gitmodules))) return;
21
-
22
- const list = await runCommand("git", ["config", "--file", ".gitmodules", "--get-regexp", "^submodule\\..*\\.path$"], {
23
- cwd: workspaceRoot,
24
- });
25
- if (list.code !== 0) {
26
- // `.gitmodules` exists but no submodule path entries: treat as ok.
27
- return;
28
- }
29
-
30
- /** @type {Array<{ name: string, path: string }>} */
31
- const subs = [];
32
- for (const line of String(list.stdout || "").split("\n")) {
33
- const t = line.trim();
34
- if (!t) continue;
35
- const idx = t.indexOf(" ");
36
- if (idx <= 0) continue;
37
- const key = t.slice(0, idx).trim();
38
- const subPath = t.slice(idx + 1).trim();
39
- const m = key.match(/^submodule\.(.+)\.path$/);
40
- if (!m) continue;
41
- const name = m[1] || "";
42
- if (!name || !subPath) continue;
43
- subs.push({ name, path: subPath });
44
- }
45
- if (subs.length === 0) return;
46
-
47
- /** @type {Array<{ name: string, path: string }>} */
48
- const missing = [];
49
- for (const s of subs) {
50
- const br = await runCommand("git", ["config", "--file", ".gitmodules", "--get", `submodule.${s.name}.branch`], {
51
- cwd: workspaceRoot,
52
- });
53
- if (br.code !== 0 || !String(br.stdout || "").trim()) {
54
- missing.push(s);
55
- }
56
- }
57
-
58
- if (missing.length > 0) {
59
- const lines = missing.map((m) => `- ${m.name} (${m.path}): missing submodule.${m.name}.branch`);
60
- const hints = missing
61
- .slice(0, 8)
62
- .map((m) => `git submodule set-branch --branch main ${m.path}`)
63
- .join("\n");
64
- throw new UserError("Submodule branch policy failed: missing `.gitmodules` branch config.", {
65
- details:
66
- `${lines.join("\n")}\n\n` +
67
- "Fix:\n" +
68
- "- Run `ws-submodule-setup` (recommended), or set per submodule:\n" +
69
- `${hints}\n\n` +
70
- "Then commit `.gitmodules` in the superproject.",
71
- });
72
- }
73
- }
74
-
75
- /**
76
- * @param {string | undefined} v
77
- */
78
- function envFlag(v) {
79
- const s = String(v || "")
80
- .trim()
81
- .toLowerCase();
82
- return s === "1" || s === "true" || s === "yes" || s === "y" || s === "on";
83
- }
84
-
85
- /**
86
- * @returns {string} e.g. 20260204-140026Z
87
- */
88
- function nowStampUtc() {
89
- const d = new Date();
90
- const y = String(d.getUTCFullYear());
91
- const m = String(d.getUTCMonth() + 1).padStart(2, "0");
92
- const day = String(d.getUTCDate()).padStart(2, "0");
93
- const hh = String(d.getUTCHours()).padStart(2, "0");
94
- const mm = String(d.getUTCMinutes()).padStart(2, "0");
95
- const ss = String(d.getUTCSeconds()).padStart(2, "0");
96
- const ms = String(d.getUTCMilliseconds()).padStart(3, "0");
97
- return `${y}${m}${day}-${hh}${mm}${ss}${ms}Z`;
98
- }
99
-
100
- /**
101
- * @returns {string} e.g. 2026-01-28T14:00:26Z
102
- */
103
- function nowIsoUtc() {
104
- return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
105
- }
106
-
107
- /**
108
- * @param {any} error
109
- * @returns {{ name: string, message: string, details?: string } | null}
110
- */
111
- function serializeError(error) {
112
- if (!error) return null;
113
- if (error instanceof UserError) {
114
- return { name: error.name, message: error.message, details: error.details };
115
- }
116
- if (error instanceof Error) {
117
- return { name: error.name, message: error.message };
118
- }
119
- return { name: "Error", message: String(error) };
120
- }
121
-
122
- /**
123
- * @param {{ targetPath: string, stamp?: boolean }} options
124
- */
125
- export async function validateCommand(options) {
126
- const stampEnabled = options.stamp === true || envFlag(process.env.AIWS_VALIDATE_STAMP);
127
- const startedAt = nowIsoUtc();
128
- const startedTs = Math.floor(Date.now() / 1000);
129
- let workspaceRoot = "";
130
- let templateId = "";
131
- let specVersion = "";
132
- let aiwsVersion = "";
133
- let status = /** @type {"ok" | "error"} */ ("ok");
134
- let exitCode = 0;
135
- let err = null;
136
- let stampPath = "";
137
-
138
- try {
139
- workspaceRoot = await resolveWorkspaceRoot(options.targetPath, { create: false });
140
- const aiws = await loadAiwsPackage();
141
- aiwsVersion = aiws.version;
142
-
143
- const manifestPath = path.join(workspaceRoot, ".aiws", "manifest.json");
144
- if (!(await pathExists(manifestPath))) {
145
- const legacyTemplateId = await detectLegacyWorkspaceTemplateId(workspaceRoot);
146
- if (legacyTemplateId) {
147
- throw new UserError("Missing .aiws/manifest.json in a legacy AIWS workspace.", {
148
- details: "This workspace looks like an older AIWS install. Run `aiws update .` once to generate the manifest, then rerun `aiws validate`.",
149
- });
150
- }
151
- throw new UserError("Missing .aiws/manifest.json. Run `aiws init` first.");
152
- }
153
- const stored = JSON.parse(await readText(manifestPath));
154
- templateId = String(stored.template_id || "workspace");
155
- const tpl = await loadTemplate(templateId);
156
- specVersion = tpl.specVersion;
157
-
158
- // Required files/dirs.
159
- const required = await expandManifestEntries(tpl.templateDir, tpl.manifest.required || []);
160
- /** @type {string[]} */
161
- const missing = [];
162
- for (const rel of required) {
163
- const abs = path.join(workspaceRoot, ...String(rel).split("/"));
164
- if (!(await pathExists(abs))) missing.push(rel);
165
- }
166
- if (missing.length > 0) {
167
- throw new UserError("Missing required files.", { details: missing.join("\n") });
168
- }
169
-
170
- // Drift detection.
171
- await validateDrift({ workspaceRoot, storedManifest: stored, templateManifest: tpl.manifest });
172
-
173
- // Submodule branch policy (deterministic workflow; avoids guessing target branches).
174
- await validateSubmoduleBranchPolicy(workspaceRoot);
175
-
176
- // python3 gate.
177
- const py = await runCommand("python3", ["--version"], { cwd: workspaceRoot });
178
- if (py.code !== 0) {
179
- throw new UserError("python3 is required for validate.", { details: py.stderr || py.stdout });
180
- }
181
-
182
- // Required gate scripts.
183
- const wsCheck = await runCommand("python3", ["tools/ws_change_check.py", "--strict"], { cwd: workspaceRoot });
184
- if (wsCheck.code !== 0) {
185
- throw new UserError("change gate failed.", { details: wsCheck.stderr || wsCheck.stdout });
186
- }
187
- const reqCheck = await runCommand("python3", ["tools/requirements_contract.py", "validate"], { cwd: workspaceRoot });
188
- if (reqCheck.code !== 0) {
189
- throw new UserError("requirements contract gate failed.", { details: reqCheck.stderr || reqCheck.stdout });
190
- }
191
-
192
- console.log(`✓ aiws validate: ${workspaceRoot}`);
193
- } catch (error) {
194
- status = "error";
195
- err = serializeError(error);
196
- if (error instanceof UserError) {
197
- exitCode = error.exitCode;
198
- } else {
199
- exitCode = 1;
200
- }
201
- throw error;
202
- } finally {
203
- if (stampEnabled && workspaceRoot) {
204
- try {
205
- const stampDir = path.join(workspaceRoot, ".agentdocs", "tmp", "aiws-validate");
206
- await ensureDir(stampDir);
207
- stampPath = path.join(stampDir, `${nowStampUtc()}.json`);
208
- const stamp = {
209
- timestamp: startedTs,
210
- ws_root: workspaceRoot,
211
- template_id: templateId || null,
212
- aiws_version: aiwsVersion || null,
213
- spec_version: specVersion || null,
214
- started_at: startedAt,
215
- finished_at: nowIsoUtc(),
216
- status,
217
- exit_code: status === "ok" ? 0 : exitCode || 1,
218
- error: err,
219
- note: "aiws validate stamp; does not include full stdout/stderr; do not put secrets in validation output.",
220
- };
221
- await writeText(stampPath, JSON.stringify(stamp, null, 2) + "\n");
222
- console.log(`stamp: ${path.relative(workspaceRoot, stampPath)}`);
223
- } catch (e) {
224
- console.error(`warn: failed to write validate stamp: ${e instanceof Error ? e.message : String(e)}`);
225
- }
226
- }
227
- }
228
- }
@@ -1,44 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { UserError } from "../errors.js";
4
-
5
- /**
6
- * aiws commit — validate + commit
7
- * @param {{ cwd?: string, message?: string }} opts
8
- */
9
- export async function wsCommitCommand({ cwd = ".", message } = {}) {
10
- checkClean(cwd);
11
-
12
- // Check review artifacts
13
- const reviewsExist = existsSync(`${cwd}/changes/*/review/spec-review.md`) ||
14
- existsSync(`${cwd}/changes/*/review/quality-review.md`);
15
- if (!reviewsExist) {
16
- console.log("warn: no spec-review or quality-review found");
17
- }
18
-
19
- // Run validate
20
- try {
21
- execSync("npx -y @aipper/aiws validate . 2>/dev/null || aiws validate . 2>/dev/null || true", { cwd, stdio: "inherit" });
22
- } catch { /* validate is advisory, not blocking */ }
23
-
24
- // Get staged changes
25
- const staged = execSync("git diff --cached --name-only", { cwd, encoding: "utf8" }).trim();
26
- if (!staged) {
27
- console.log("info: nothing staged. Use git add first.");
28
- }
29
-
30
- // Commit
31
- if (!message) {
32
- throw new UserError("commit requires --message (use -m or --message)", { exitCode: 2 });
33
- }
34
-
35
- execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd, stdio: "inherit" });
36
- console.log("ok: commit created");
37
- }
38
-
39
- function checkClean(cwd) {
40
- const out = execSync("git status --porcelain", { cwd, encoding: "utf8" }).trim();
41
- if (!out) {
42
- throw new UserError("Nothing to commit. Working tree clean.", { exitCode: 2 });
43
- }
44
- }
@@ -1,67 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { UserError } from "../errors.js";
4
-
5
- /**
6
- * aiws deliver — submodule + superproject commit, then finish
7
- * @param {{ cwd?: string, message?: string, changeId?: string }} opts
8
- */
9
- export async function wsDeliverCommand({ cwd = ".", message, changeId } = {}) {
10
- if (!changeId) {
11
- changeId = detectChangeBranch(cwd);
12
- }
13
- if (!changeId) {
14
- throw new UserError("Could not detect change ID.", { exitCode: 2 });
15
- }
16
-
17
- if (!message) {
18
- throw new UserError("deliver requires --message for the superproject commit", { exitCode: 2 });
19
- }
20
-
21
- console.log("info: starting delivery...");
22
-
23
- // Commit submodules first
24
- if (existsSync(`${cwd}/.gitmodules`)) {
25
- const out = execSync("git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$'", {
26
- cwd, encoding: "utf8",
27
- }).trim();
28
-
29
- for (const line of out.split("\n")) {
30
- const parts = line.trim().split(/\s+/);
31
- if (parts.length < 2) continue;
32
- const subPath = parts[1];
33
-
34
- const status = execSync(`git -C "${subPath}" status --porcelain`, { cwd, encoding: "utf8" }).trim();
35
- if (status) {
36
- console.log(`info: submodule ${subPath} has changes. Committing...`);
37
- execSync(`git -C "${subPath}" add -A`, { cwd, stdio: "inherit" });
38
- execSync(`git -C "${subPath}" commit -m "${message}"`, { cwd, stdio: "inherit" });
39
- } else {
40
- console.log(`info: submodule ${subPath} has no changes, skipping`);
41
- }
42
- }
43
- }
44
-
45
- // Commit superproject
46
- const superStatus = execSync("git status --porcelain", { cwd, encoding: "utf8" }).trim();
47
- if (superStatus) {
48
- console.log("info: committing superproject...");
49
- execSync("git add -A", { cwd, stdio: "inherit" });
50
- execSync(`git commit -m "${message}"`, { cwd, stdio: "inherit" });
51
- } else {
52
- console.log("info: superproject has no changes");
53
- }
54
-
55
- // Finish
56
- await import("./ws-finish.js").then(m =>
57
- m.wsFinishCommand({ cwd, changeId, push: true })
58
- );
59
-
60
- console.log("ok: deliver complete");
61
- }
62
-
63
- function detectChangeBranch(cwd) {
64
- const branch = execSync("git branch --show-current", { cwd, encoding: "utf8" }).trim();
65
- const match = branch.match(/^(?:change|changes|ws|ws-change)\/(.+)$/);
66
- return match ? match[1] : null;
67
- }
@@ -1,83 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { UserError } from "../errors.js";
4
-
5
- /**
6
- * aiws finish — ff-merge + push + archive
7
- * @param {{ cwd?: string, changeId?: string, into?: string, push?: boolean, remote?: string }} opts
8
- */
9
- export async function wsFinishCommand({ cwd = ".", changeId, into, push = false, remote = "origin" } = {}) {
10
- checkClean(cwd);
11
-
12
- if (!changeId) {
13
- changeId = detectChangeBranch(cwd);
14
- }
15
-
16
- if (!changeId) {
17
- throw new UserError("Could not detect change ID. Provide as argument or be on a change branch.", { exitCode: 2 });
18
- }
19
-
20
- const targetBranch = into || "main";
21
-
22
- // Validate before complete
23
- try {
24
- execSync(`npx -y @aipper/aiws change validate ${changeId} --strict 2>/dev/null`, { cwd, stdio: "inherit" });
25
- } catch {
26
- throw new UserError(`Validation failed for ${changeId}. Fix issues first.`, { exitCode: 1 });
27
- }
28
-
29
- // FF merge
30
- const changeBranch = `change/${changeId}`;
31
- const branchExists = execSync(`git branch --list '${changeBranch}'`, { cwd, encoding: "utf8" }).trim();
32
- if (branchExists) {
33
- console.log(`info: ff-merging ${changeBranch} → ${targetBranch}...`);
34
- execSync(`git checkout ${targetBranch}`, { cwd, stdio: "inherit" });
35
- execSync(`git merge --ff-only ${changeBranch}`, { cwd, stdio: "inherit" });
36
- } else {
37
- console.log("info: no change branch found, using current branch");
38
- }
39
-
40
- // Push
41
- if (push) {
42
- const currentBranch = execSync("git branch --show-current", { cwd, encoding: "utf8" }).trim();
43
-
44
- // Push submodules first
45
- if (existsSync(`${cwd}/.gitmodules`)) {
46
- const targetsFile = `changes/${changeId}/submodules.targets`;
47
- if (existsSync(targetsFile)) {
48
- const content = execSync(`cat "${targetsFile}"`, { cwd, encoding: "utf8" });
49
- for (const line of content.split("\n")) {
50
- const t = line.trim();
51
- if (!t || t.startsWith("#")) continue;
52
- const [subPath, targetBranch] = t.split(/\s+/);
53
- if (subPath && targetBranch) {
54
- console.log(`info: pushing submodule ${subPath}...`);
55
- execSync(`git push ${remote} ${targetBranch}`, { cwd: `${cwd}/${subPath}`, stdio: "inherit" });
56
- }
57
- }
58
- }
59
- }
60
-
61
- execSync(`git push ${remote} ${currentBranch}`, { cwd, stdio: "inherit" });
62
- }
63
-
64
- // Archive
65
- try {
66
- execSync(`npx -y @aipper/aiws change archive ${changeId} --force 2>/dev/null || aiws change archive ${changeId} --force 2>/dev/null || true`, { cwd, stdio: "inherit" });
67
- } catch { /* ignore archive failure — main flow done */ }
68
-
69
- console.log(`ok: finish complete. ${push ? "Pushed and " : ""}Archived.`);
70
- }
71
-
72
- function checkClean(cwd) {
73
- const out = execSync("git status --porcelain", { cwd, encoding: "utf8" }).trim();
74
- if (out) {
75
- throw new UserError("Working tree is not clean. Commit or stash first.", { exitCode: 2 });
76
- }
77
- }
78
-
79
- function detectChangeBranch(cwd) {
80
- const branch = execSync("git branch --show-current", { cwd, encoding: "utf8" }).trim();
81
- const match = branch.match(/^(?:change|changes|ws|ws-change)\/(.+)$/);
82
- return match ? match[1] : null;
83
- }
@@ -1,40 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync } from "node:fs";
2
-
3
- /**
4
- * aiws handoff — read/display handoff documents
5
- * @param {{ cwd?: string }} opts
6
- */
7
- export async function wsHandoffCommand({ cwd = "." } = {}) {
8
- const changesDir = `${cwd}/changes`;
9
- if (!existsSync(changesDir)) {
10
- console.log("info: no changes/ directory found");
11
- return;
12
- }
13
-
14
- // Check archive handoffs first
15
- const archiveDir = `${changesDir}/archive`;
16
- if (existsSync(archiveDir)) {
17
- const archives = readdirSync(archiveDir).filter(f => f !== ".DS_Store");
18
- for (const a of archives) {
19
- const hf = `${archiveDir}/${a}/handoff.md`;
20
- if (existsSync(hf)) {
21
- console.log(`=== ${a} ===`);
22
- console.log(readFileSync(hf, "utf8"));
23
- }
24
- }
25
- }
26
-
27
- // Check active change handoffs
28
- const changes = readdirSync(changesDir).filter(f =>
29
- !f.startsWith(".") && f !== "templates" && f !== "archive" && f !== "README.md"
30
- );
31
- for (const c of changes) {
32
- const hf = `${changesDir}/${c}/handoff.md`;
33
- if (existsSync(hf)) {
34
- console.log(`=== ${c} (active) ===`);
35
- console.log(readFileSync(hf, "utf8"));
36
- }
37
- }
38
-
39
- console.log("info: handoffs are generated by aiws change finish --push");
40
- }
@@ -1,41 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
-
4
- /**
5
- * aiws migrate — workspace migration (init or update)
6
- * @param {{ cwd?: string }} opts
7
- */
8
- export async function wsMigrateCommand({ cwd = "." } = {}) {
9
- const localAiws = `${cwd}/node_modules/.bin/aiws`;
10
-
11
- if (existsSync(`${cwd}/.aiws/manifest.json`)) {
12
- console.log("info: workspace detected, running update...");
13
- runAiws(localAiws, `update ${cwd}`);
14
- } else {
15
- console.log("info: no workspace detected, running init...");
16
- runAiws(localAiws, `init ${cwd}`);
17
- }
18
-
19
- // Enable hooks
20
- try {
21
- execSync("git config core.hooksPath .githooks", { cwd, stdio: "inherit" });
22
- console.log("info: hooks enabled (core.hooksPath=.githooks)");
23
- } catch {
24
- console.log("warn: could not set hooks (not a git repo?)");
25
- }
26
-
27
- console.log("ok: migrate complete");
28
- }
29
-
30
- function runAiws(localAiws, args) {
31
- try {
32
- if (existsSync(localAiws)) {
33
- execSync(`${localAiws} ${args}`, { stdio: "inherit" });
34
- } else {
35
- execSync(`npx -y @aipper/aiws ${args}`, { stdio: "inherit" });
36
- }
37
- } catch (e) {
38
- console.error(`error: aiws ${args} failed`);
39
- throw e;
40
- }
41
- }