@zaip-ai/cli 4.5.0 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +2 -0
  2. package/dist/commands/init.js +1 -1
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/services/adapters.d.ts +1 -1
  5. package/dist/services/adapters.d.ts.map +1 -1
  6. package/dist/services/adapters.js +6 -6
  7. package/dist/services/adapters.js.map +1 -1
  8. package/dist/services/codex-config-overlay.d.ts.map +1 -1
  9. package/dist/services/codex-config-overlay.js +27 -10
  10. package/dist/services/codex-config-overlay.js.map +1 -1
  11. package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
  12. package/dist/services/codex-native-tool-authority-template.js +188 -9
  13. package/dist/services/codex-native-tool-authority-template.js.map +1 -1
  14. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  15. package/dist/services/codex-pre-tool-hook-template.js +50 -128
  16. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  17. package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
  18. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  19. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  20. package/dist/services/codex-pre-tool-support-template.js +1 -4
  21. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  22. package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
  23. package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
  24. package/dist/services/codex-stale-git-lock-template.js +64 -0
  25. package/dist/services/codex-stale-git-lock-template.js.map +1 -0
  26. package/dist/services/global-codex-config-migration.test.js +1 -4
  27. package/dist/services/global-codex-config-migration.test.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts.map +1 -1
  29. package/dist/services/global-codex-config.js +2 -1
  30. package/dist/services/global-codex-config.js.map +1 -1
  31. package/dist/services/global-codex-config.test.js +3 -1
  32. package/dist/services/global-codex-config.test.js.map +1 -1
  33. package/dist/services/hook-readiness.d.ts.map +1 -1
  34. package/dist/services/hook-readiness.js.map +1 -1
  35. package/dist/services/install-agents-template.js +2 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/install-template-policy.d.ts.map +1 -1
  38. package/dist/services/install-template-policy.js +2 -0
  39. package/dist/services/install-template-policy.js.map +1 -1
  40. package/dist/services/logger.d.ts +1 -1
  41. package/dist/services/logger.d.ts.map +1 -1
  42. package/dist/services/logger.js +2 -1
  43. package/dist/services/logger.js.map +1 -1
  44. package/dist/services/logger.test.js +77 -0
  45. package/dist/services/logger.test.js.map +1 -1
  46. package/dist/services/runtime-template-integrity.test.d.ts +2 -0
  47. package/dist/services/runtime-template-integrity.test.d.ts.map +1 -0
  48. package/dist/services/runtime-template-integrity.test.js +26 -0
  49. package/dist/services/runtime-template-integrity.test.js.map +1 -0
  50. package/dist/services/runtime-template-test-support.d.ts +3 -0
  51. package/dist/services/runtime-template-test-support.d.ts.map +1 -0
  52. package/dist/services/runtime-template-test-support.js +32 -0
  53. package/dist/services/runtime-template-test-support.js.map +1 -0
  54. package/dist/services/runtime-template.test.js +37 -48
  55. package/dist/services/runtime-template.test.js.map +1 -1
  56. package/package.json +1 -1
  57. package/templates/.agents/README.md +8 -0
  58. package/templates/.agents/config/agents/discovery.json +1 -1
  59. package/templates/.agents/config/deploy-policy.json +5 -0
  60. package/templates/.agents/dashboard/.zaip-managed.json +235 -83
  61. package/templates/.agents/dashboard/README.md +2 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-OUjdwEcM.js} +1 -1
  63. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-Cei1ChjE.js} +99 -10
  64. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-_woNCrTT.js} +1 -1
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-B2_7MVOP.js} +1 -1
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-C_jCsSZe.js} +1 -1
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C8zQDBT7.js +68 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-iMPXot7Y.js} +1 -1
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DiCwz0sr.js +2 -0
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-721Re3Ss.js} +1 -1
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-BjdTkWS0.js} +1 -1
  73. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  74. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  75. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
  76. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
  77. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
  78. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
  79. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
  80. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
  81. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
  86. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
  87. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  88. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
  89. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
  90. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  91. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +7 -2
  92. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +6 -6
  93. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
  94. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
  95. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
  96. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
  97. package/templates/.agents/dashboard/modules/operations/src/react/overview-duration-hint.ts +14 -0
  98. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  99. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  100. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  101. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  102. package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
  103. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
  104. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
  105. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
  106. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +318 -236
  107. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +285 -205
  108. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  109. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
  110. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
  111. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
  112. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
  113. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +318 -236
  114. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
  115. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
  116. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
  117. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
  118. package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
  119. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
  120. package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
  121. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
  123. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
  124. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
  125. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
  126. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
  127. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
  128. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
  129. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
  130. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
  131. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
  132. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
  133. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
  134. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
  135. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-summary-elapsed.ts +107 -0
  136. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
  139. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
  140. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
  143. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +41 -54
  144. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
  145. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +7 -7
  146. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +5 -5
  147. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +4 -4
  148. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
  149. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +13 -9
  150. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +9 -7
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +10 -8
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed-view.ts +61 -0
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed.test.ts +210 -0
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
  159. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
  160. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
  161. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  162. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
  163. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
  164. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
  165. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  166. package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
  167. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
  168. package/templates/.agents/design/.zaip-managed.json +17 -5
  169. package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
  170. package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
  171. package/templates/.agents/design/apps/catalog/package.json +1 -1
  172. package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
  173. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
  174. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
  175. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  176. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  177. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  178. package/templates/.agents/requirements/comparative-audit.md +1 -1
  179. package/templates/.agents/requirements/context-engine.md +1 -1
  180. package/templates/.agents/requirements/cost-observability.md +1 -1
  181. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  182. package/templates/.agents/requirements/data-export.md +1 -1
  183. package/templates/.agents/requirements/design-system.md +1 -1
  184. package/templates/.agents/requirements/execution-engine.md +1 -1
  185. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  186. package/templates/.agents/requirements/model-orchestration.md +21 -6
  187. package/templates/.agents/requirements/public-distribution.md +4 -4
  188. package/templates/.agents/requirements/software-architecture.md +1 -1
  189. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  190. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  191. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  192. package/templates/.agents/rules/core.md +3 -1
  193. package/templates/.agents/rules/workflow.md +3 -2
  194. package/templates/.agents/runtime/VERSION +1 -1
  195. package/templates/.agents/runtime/bin/zaip.mjs +368 -199
  196. package/templates/.agents/runtime/checksums.sha256 +5 -5
  197. package/templates/.agents/runtime/manifest.json +1 -1
  198. package/templates/.agents/runtime/provenance.json +4 -4
  199. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  200. package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
  201. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
  202. package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
  203. package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
  204. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
  205. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
  206. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +38 -48
  207. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  208. package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
  209. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
  210. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +307 -0
  211. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +337 -0
  212. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
  213. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
  214. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
  215. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
  216. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +46 -68
  217. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +100 -44
  218. package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
  219. package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
  220. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
  221. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
  222. package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
  223. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
  224. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
  225. package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
  226. package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
  227. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
  228. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
  229. package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
  230. package/templates/.agents/zaip-aios/VERSION +1 -1
  231. package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
  232. package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
  233. package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
  234. package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
  235. package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
  236. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
  237. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
  238. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
  239. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
  240. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
  241. package/templates/template-manifest.json +354 -150
  242. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
  243. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
  244. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
@@ -0,0 +1,307 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { readFile, realpath, writeFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parseArgs } from "node:util";
7
+
8
+ export const POLICY_PATH = ".agents/config/deploy-policy.json";
9
+ export const DEFAULT_POLICY = Object.freeze({
10
+ schema_version: 1,
11
+ default_mode: "auto",
12
+ default_scope: "current-work",
13
+ });
14
+ const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
15
+ const git = (root, args) =>
16
+ execFileSync("git", args, {
17
+ cwd: root,
18
+ encoding: "utf8",
19
+ windowsHide: true,
20
+ timeout: 15000,
21
+ }).trim();
22
+ function assert(ok, message) {
23
+ if (!ok) throw new Error(message);
24
+ }
25
+ export function validatePolicy(policy = DEFAULT_POLICY) {
26
+ assert(policy && typeof policy === "object" && !Array.isArray(policy), "Invalid deploy policy");
27
+ assert(
28
+ Object.keys(policy).every((k) =>
29
+ ["schema_version", "default_mode", "default_scope"].includes(k),
30
+ ),
31
+ "Unsupported deploy policy field",
32
+ );
33
+ assert(
34
+ policy.schema_version === 1 &&
35
+ ["auto", "commits", "release"].includes(policy.default_mode) &&
36
+ policy.default_scope === "current-work",
37
+ "Invalid deploy policy; scope must be current-work",
38
+ );
39
+ return { ...policy };
40
+ }
41
+ export function repositoryKind({
42
+ origin,
43
+ rootName,
44
+ cliName,
45
+ workspaceVersion,
46
+ rootVersion,
47
+ cliVersion,
48
+ }) {
49
+ const canonicalOrigin =
50
+ /^(?:git@github\.com:|https:\/\/github\.com\/|ssh:\/\/git@github\.com\/)zaip-ai\/zaip-aios(?:\.git)?\/?$/.test(
51
+ origin ?? "",
52
+ );
53
+ const canonicalRoot = rootName === "@zaip-ai/aios";
54
+ if (!canonicalOrigin && !canonicalRoot) return "consumer";
55
+ assert(
56
+ canonicalOrigin && canonicalRoot && cliName === "@zaip-ai/cli",
57
+ "Conflicting builder identity; repository and package manifests must agree",
58
+ );
59
+ assert(
60
+ /^\d+\.\d+\.0$/.test(rootVersion ?? "") &&
61
+ rootVersion === cliVersion &&
62
+ rootVersion === workspaceVersion,
63
+ "Builder manifest versions must agree",
64
+ );
65
+ return "builder";
66
+ }
67
+ const normalize = (value) =>
68
+ value
69
+ .normalize("NFKD")
70
+ .replace(/[\u0300-\u036f]/g, "")
71
+ .toLowerCase()
72
+ .replace(/\s+/g, " ")
73
+ .trim();
74
+ export function requestedDeployment(request) {
75
+ assert(
76
+ typeof request === "string" && request.length > 0 && request.length <= 8192,
77
+ "A bounded current human request is required",
78
+ );
79
+ const text = normalize(request);
80
+ // Only a direct publication imperative is executable. References, questions and quoted
81
+ // historical instructions are not authority; unknown wording never upgrades the mode.
82
+ const withoutCourtesy = text.replace(/^(?:(?:ok|por favor|agora|please)[, ]+)+/, "");
83
+ const sequencedDirect =
84
+ /^primeiro passo entao e corrigir\b[^\n?]*\bfazer merge\b[^\n?]*,\s*fazer deploy\b/.test(
85
+ withoutCourtesy,
86
+ ) && !/\b(?:nao|sem)\s+fazer deploy\b/.test(withoutCourtesy);
87
+ const releaseDirective = /\bpreciso que crie (?:uma |a )?nova versao\s+v?\d+\.\d+\b/.test(
88
+ withoutCourtesy,
89
+ );
90
+ const proceedToVersion =
91
+ /^(?:pode\s+)?(?:seguir|continue|avancar)\s+para\s+(?:a\s+)?v\d+\.\d+\b/.test(withoutCourtesy);
92
+ const direct =
93
+ /^(?:(?:pode|podemos)\s+)?(?:(?:faca|fazer|faz|realize|execute|do)\s+)?(?:deploy|dpeloy|depoly|push|commit|publique|publicar|publish|envie|release|prepare|preparar)\b/.test(
94
+ withoutCourtesy,
95
+ ) ||
96
+ sequencedDirect ||
97
+ releaseDirective ||
98
+ proceedToVersion;
99
+ if (!direct || /\?/.test(text))
100
+ return { authorized: false, reason: "not-a-direct-deployment-request" };
101
+ const noRelease =
102
+ /\b(?:sem|nao)\s+(?:(?:quero|criar|fazer|publicar|publique|crie|faca)\s+)?(?:(?:um|uma|o|a|novo|nova)\s+)*(?:release|nova versao|proxima versao|versao)\b/.test(
103
+ text,
104
+ ) ||
105
+ /\b(?:apenas|somente|so)\s+(?:(?:os|dos|o)\s+)?(?:commits?|push)\b/.test(text) ||
106
+ /\bdeploy\s+dos?\s+com{1,2}its?\s+na\s+versao\s+atual\b/.test(text);
107
+ const releaseTarget =
108
+ "(?:(?:do|da|de|um|uma|o|a|novo|nova|proximo|proxima|the|new|next)\\s+)*(?:release|versao|version|v\\d+\\.\\d+)";
109
+ const releaseMention =
110
+ new RegExp(
111
+ `\\b(?:deploy|dpeloy|depoly|publique|publicar|publish|release|prepare|preparar)\\s+(?:com\\s+)?${releaseTarget}\\b`,
112
+ ).test(text) ||
113
+ /^release\b/.test(withoutCourtesy) ||
114
+ releaseDirective ||
115
+ proceedToVersion;
116
+ const nonPublication = text.replace(
117
+ /\bnao\s+(?:publique|publicar)\s+(?:(?:um|uma|o|a|novo|nova)\s+)*release\b/g,
118
+ "",
119
+ );
120
+ const preparation =
121
+ /^(?:(?:pode)\s+)?(?:prepare|preparar)\b/.test(withoutCourtesy) ||
122
+ /\b(?:apenas|somente|so)\s+prepar/.test(text) ||
123
+ /\b(?:sem publicar|nao publique|nao publicar)\b/.test(nonPublication);
124
+ const contradictory =
125
+ noRelease && /\be\s+(?:publique|crie|faca)\s+(?:(?:um|o|novo)\s+)*release\b/.test(text);
126
+ assert(!contradictory, "Conflicting publication instructions; no action is authorized");
127
+ const pushOnly =
128
+ /^(?:(?:pode|podemos)\s+)?(?:(?:faca|fazer|faz|realize|execute|do)\s+)?(?:push|commit)\b/.test(
129
+ withoutCourtesy,
130
+ );
131
+ const versions = [...text.matchAll(/\bv((?:0|[1-9]\d*)\.(?:0|[1-9]\d*))(?![.\d])/g)].map(
132
+ (x) => x[1],
133
+ );
134
+ if (releaseDirective)
135
+ versions.push(/\bpreciso que crie (?:uma |a )?nova versao\s+v?(\d+\.\d+)\b/.exec(text)[1]);
136
+ assert(new Set(versions).size <= 1, "Conflicting explicit release versions");
137
+ return {
138
+ authorized: true,
139
+ override: noRelease ? "commits" : releaseMention ? "release" : pushOnly ? "commits" : null,
140
+ version: !noRelease && releaseMention ? (versions[0] ?? null) : null,
141
+ phase: preparation ? "prepare" : "publish",
142
+ scope: /\b(?:deploy|push|publicar|publique)\s+(?:de\s+)?tudo\b/.test(text)
143
+ ? "all-worktree"
144
+ : "current-work",
145
+ };
146
+ }
147
+ export function resolveDeployment({ request, policy, identity }) {
148
+ const preference = validatePolicy(policy);
149
+ const kind = repositoryKind(identity);
150
+ const intent = requestedDeployment(request);
151
+ if (!intent.authorized)
152
+ return { schema_version: 1, mode: "none", phase: "none", reason: intent.reason };
153
+ const configuredMode =
154
+ preference.default_mode === "auto"
155
+ ? kind === "builder"
156
+ ? "release"
157
+ : "commits"
158
+ : preference.default_mode;
159
+ const mode = intent.override ?? configuredMode;
160
+ const authorization = {
161
+ schema_version: 1,
162
+ kind,
163
+ mode,
164
+ phase: intent.phase,
165
+ scope: intent.scope,
166
+ version: intent.version,
167
+ policy_sha256: digest(preference),
168
+ request_sha256: digest(request),
169
+ identity_sha256: digest(identity),
170
+ changes_preference: false,
171
+ qualification: mode === "release" ? "release-essential" : "impact",
172
+ npm_publication: kind === "builder" && mode === "release" && intent.phase === "publish",
173
+ chat_output: "brief",
174
+ release_notes: mode === "release" ? "reviewed-full-range" : "none",
175
+ };
176
+ return { ...authorization, contract_sha256: digest(authorization) };
177
+ }
178
+ export function verifyContract(plan, input, expectedMode, expectedPhase = "publish") {
179
+ const actual = resolveDeployment(input);
180
+ assert(
181
+ actual.mode === expectedMode && actual.phase === expectedPhase,
182
+ "Deployment intent does not authorize this delivery route",
183
+ );
184
+ assert(
185
+ JSON.stringify(plan) === JSON.stringify(actual),
186
+ "Deployment contract changed; resolve again before any mutation",
187
+ );
188
+ return actual;
189
+ }
190
+ export function nextRelease(
191
+ tags = [],
192
+ {
193
+ major = false,
194
+ publishedSha = null,
195
+ candidateSha = null,
196
+ publishedTag = null,
197
+ existingTag = null,
198
+ existingTagSha = null,
199
+ } = {},
200
+ ) {
201
+ assert(
202
+ Array.isArray(tags) && tags.every((x) => typeof x === "string"),
203
+ "Verified tag list required",
204
+ );
205
+ const valid = tags
206
+ .map((tag) => /^v(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/.exec(tag))
207
+ .filter(Boolean);
208
+ valid.sort((a, b) =>
209
+ BigInt(a[1]) === BigInt(b[1])
210
+ ? BigInt(a[2]) < BigInt(b[2])
211
+ ? -1
212
+ : BigInt(a[2]) > BigInt(b[2])
213
+ ? 1
214
+ : 0
215
+ : BigInt(a[1]) < BigInt(b[1])
216
+ ? -1
217
+ : 1,
218
+ );
219
+ if (publishedSha && publishedSha === candidateSha) {
220
+ assert(
221
+ /^[a-f0-9]{40}$/i.test(candidateSha ?? "") &&
222
+ tags.includes(publishedTag) &&
223
+ /^v(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(publishedTag ?? ""),
224
+ "Published identity must include its verified tag and SHA",
225
+ );
226
+ return { action: "verify-existing", tag: publishedTag, create_release: false };
227
+ }
228
+ if (existingTag) {
229
+ assert(
230
+ /^v(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(existingTag) &&
231
+ /^[a-f0-9]{40}$/i.test(candidateSha ?? "") &&
232
+ existingTagSha === candidateSha,
233
+ "Retry requires the exact existing tag and candidate SHA",
234
+ );
235
+ return { action: "resume", tag: existingTag, create_release: false };
236
+ }
237
+ const last = valid.at(-1);
238
+ const version = !last
239
+ ? "1.0"
240
+ : major
241
+ ? `${BigInt(last[1]) + 1n}.0`
242
+ : `${last[1]}.${BigInt(last[2]) + 1n}`;
243
+ return { action: "new", tag: `v${version}`, version, create_release: true };
244
+ }
245
+ export async function readDeploymentInput(repository, request) {
246
+ const root = await realpath(resolve(repository));
247
+ assert(
248
+ (await realpath(git(root, ["rev-parse", "--show-toplevel"]))) === root,
249
+ "Use the exact Git workspace root",
250
+ );
251
+ const readJson = async (path, fallback = null) => {
252
+ try {
253
+ return JSON.parse(await readFile(resolve(root, path), "utf8"));
254
+ } catch (error) {
255
+ if (error.code === "ENOENT") return fallback;
256
+ throw error;
257
+ }
258
+ };
259
+ const [manifest, cli, workspace, policy] = await Promise.all([
260
+ readJson("package.json"),
261
+ readJson("public-cli/packages/cli/package.json"),
262
+ readJson("public-cli/package.json"),
263
+ readJson(POLICY_PATH, DEFAULT_POLICY),
264
+ ]);
265
+ let origin = null;
266
+ try {
267
+ origin = git(root, ["remote", "get-url", "--push", "origin"]);
268
+ } catch {}
269
+ return {
270
+ request,
271
+ policy,
272
+ identity: {
273
+ origin,
274
+ rootName: manifest?.name ?? null,
275
+ cliName: cli?.name ?? null,
276
+ rootVersion: manifest?.version ?? null,
277
+ cliVersion: cli?.version ?? null,
278
+ workspaceVersion: workspace?.version ?? null,
279
+ },
280
+ };
281
+ }
282
+ async function main() {
283
+ const { values } = parseArgs({
284
+ options: {
285
+ repository: { type: "string", default: "." },
286
+ "request-file": { type: "string" },
287
+ "plan-file": { type: "string" },
288
+ },
289
+ strict: true,
290
+ });
291
+ assert(values["request-file"], "--request-file with the current human request is required");
292
+ const input = await readDeploymentInput(
293
+ values.repository,
294
+ await readFile(resolve(values["request-file"]), "utf8"),
295
+ );
296
+ const plan = resolveDeployment(input);
297
+ if (values["plan-file"])
298
+ await writeFile(resolve(values["plan-file"]), `${JSON.stringify(plan, null, 2)}\n`, {
299
+ flag: "wx",
300
+ });
301
+ process.stdout.write(`${JSON.stringify(plan)}\n`);
302
+ }
303
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url))
304
+ main().catch((error) => {
305
+ process.stderr.write(`${error.message}\n`);
306
+ process.exitCode = 1;
307
+ });
@@ -0,0 +1,337 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import test from "node:test";
7
+ import {
8
+ DEFAULT_POLICY,
9
+ nextRelease,
10
+ readDeploymentInput,
11
+ repositoryKind,
12
+ resolveDeployment,
13
+ verifyContract,
14
+ } from "./deploy-contract.mjs";
15
+ import { criticalTaskInRange, guardedCommits } from "./guarded-commits.mjs";
16
+
17
+ const builder = {
18
+ origin: "git@github.com:zaip-ai/zaip-aios.git",
19
+ rootName: "@zaip-ai/aios",
20
+ cliName: "@zaip-ai/cli",
21
+ rootVersion: "4.5.0",
22
+ cliVersion: "4.5.0",
23
+ workspaceVersion: "4.5.0",
24
+ };
25
+ const consumer = { origin: "git@github.com:example/app.git", rootName: "app" };
26
+ const input = (request, identity = consumer, policy = DEFAULT_POLICY) => ({
27
+ request,
28
+ identity,
29
+ policy,
30
+ });
31
+
32
+ test("fixed defaults, explicit intent and preparation: Portuguese/English decision matrix", () => {
33
+ const cases = [
34
+ ["faça deploy", null, "publish"],
35
+ ["pode fazer deploy", null, "publish"],
36
+ ["ok, agora faça deploy", null, "publish"],
37
+ ["faça dpeloy", null, "publish"],
38
+ ["faça deploy apenas dos commits", "commits", "publish"],
39
+ ["faça push", "commits", "publish"],
40
+ ["push", "commits", "publish"],
41
+ ["faça deploy sem release", "commits", "publish"],
42
+ ["faça deploy, não quero release", "commits", "publish"],
43
+ ["faça deploy, mas não publique release", "commits", "publish"],
44
+ ["faça deploy com release", "release", "publish"],
45
+ ["faça deploy da próxima versão", "release", "publish"],
46
+ ["faça deploy do novo release", "release", "publish"],
47
+ ["publique novo release", "release", "publish"],
48
+ ["publique a nova versão", "release", "publish"],
49
+ ["publish a release", "release", "publish"],
50
+ ["deploy release", "release", "publish"],
51
+ ["publish new release", "release", "publish"],
52
+ ["deploy next version", "release", "publish"],
53
+ ["faça deploy v4.6", "release", "publish"],
54
+ ["pode seguir para v4.7, mas garanta que o dashboard esteja correto", "release", "publish"],
55
+ ["prepare a próxima versão", "release", "prepare"],
56
+ ["faça deploy release sem publicar", "release", "prepare"],
57
+ ["faça deploy e explique quando devo pedir release", null, "publish"],
58
+ ];
59
+ for (const identity of [builder, consumer])
60
+ for (const [request, override, phase] of cases) {
61
+ const plan = resolveDeployment(input(request, identity));
62
+ assert.equal(plan.mode, override ?? (identity === builder ? "release" : "commits"), request);
63
+ assert.equal(plan.phase, phase, request);
64
+ assert.equal(
65
+ plan.npm_publication,
66
+ identity === builder && plan.mode === "release" && phase === "publish",
67
+ );
68
+ assert.equal(plan.scope, "current-work");
69
+ assert.equal(plan.changes_preference, false);
70
+ }
71
+ });
72
+ test("explicit current-version sequence stays commits-only and a requested new version selects release", () => {
73
+ const earlier =
74
+ "ok, primeiro passo então é corrigir as branchs atuais e fazer merge de tudo pra main, fazer deploy dos comits na versão atual.";
75
+ assert.equal(resolveDeployment(input(earlier, builder)).mode, "commits");
76
+ const current =
77
+ "Resolva essas pendencias, preciso que crie uma nova versao 4.6 e faca mesclagem e arquivamentos de branchs.";
78
+ const plan = resolveDeployment(input(current, builder));
79
+ assert.equal(plan.mode, "release");
80
+ assert.equal(plan.version, "4.6");
81
+ assert.equal(plan.phase, "publish");
82
+ });
83
+ test("discussion, quoted history, negation and questions never authorize publication", () => {
84
+ for (const request of [
85
+ "não faça deploy",
86
+ "explique deploy com release",
87
+ "o usuário disse 'faça deploy'",
88
+ '"faça deploy"',
89
+ "seria melhor publicar um release?",
90
+ "pode fazer deploy?",
91
+ "implemente a regra de deploy",
92
+ ]) {
93
+ assert.equal(resolveDeployment(input(request, builder)).mode, "none", request);
94
+ }
95
+ assert.throws(
96
+ () => resolveDeployment(input("faça deploy sem release e publique release")),
97
+ /Conflicting/,
98
+ );
99
+ assert.throws(() => resolveDeployment(input("faça deploy release v4.6 e v4.7")), /versions/);
100
+ assert.equal(resolveDeployment(input("faça deploy v4.6")).version, "4.6");
101
+ assert.equal(resolveDeployment(input("pode seguir para v4.7", builder)).version, "4.7");
102
+ assert.equal(resolveDeployment(input("pode seguir para v4.7?", builder)).mode, "none");
103
+ });
104
+ test("preferences are project-owned; one-shot overrides, scope and invalid config are explicit", () => {
105
+ const preference = { ...DEFAULT_POLICY, default_mode: "release" };
106
+ assert.equal(resolveDeployment(input("faça deploy", consumer, preference)).mode, "release");
107
+ assert.equal(
108
+ resolveDeployment(input("faça deploy apenas commits", consumer, preference)).mode,
109
+ "commits",
110
+ );
111
+ assert.equal(preference.default_mode, "release");
112
+ assert.equal(resolveDeployment(input("faça deploy de tudo")).scope, "all-worktree");
113
+ for (const policy of [
114
+ null,
115
+ {},
116
+ { ...DEFAULT_POLICY, default_mode: "maybe" },
117
+ { ...DEFAULT_POLICY, default_scope: "all-worktree" },
118
+ { ...DEFAULT_POLICY, typo: true },
119
+ ])
120
+ assert.throws(() => resolveDeployment(input("faça deploy", consumer, policy)), /policy/);
121
+ });
122
+ test("builder identity requires origin plus all three agreeing manifests", () => {
123
+ assert.equal(repositoryKind(builder), "builder");
124
+ assert.equal(repositoryKind(consumer), "consumer");
125
+ for (const identity of [
126
+ { ...builder, origin: consumer.origin },
127
+ { ...builder, rootName: "app" },
128
+ { ...builder, cliName: "other" },
129
+ { ...builder, cliVersion: "4.6.0" },
130
+ ])
131
+ assert.throws(() => repositoryKind(identity), /identity|versions/);
132
+ });
133
+ test("frozen contract rejects changed policy, request, identity or route", () => {
134
+ const current = input("faça deploy");
135
+ const plan = resolveDeployment(current);
136
+ assert.deepEqual(verifyContract(plan, current, "commits"), plan);
137
+ for (const changed of [
138
+ { ...current, request: "faça deploy release" },
139
+ { ...current, policy: { ...DEFAULT_POLICY, default_mode: "commits" } },
140
+ { ...current, identity: { ...consumer, rootName: "renamed" } },
141
+ ])
142
+ assert.throws(() => verifyContract(plan, changed, "commits"), /intent|changed/);
143
+ assert.throws(() => verifyContract(plan, current, "release"), /intent/);
144
+ });
145
+ test("numeric version progression, explicit major, exact retry and no empty release", () => {
146
+ assert.equal(nextRelease().tag, "v1.0");
147
+ assert.equal(nextRelease(["v4.9", "v4.10", "v4.2", "junk"]).tag, "v4.11");
148
+ assert.equal(nextRelease(["v4.10"], { major: true }).tag, "v5.0");
149
+ const sha = "a".repeat(40);
150
+ assert.equal(
151
+ nextRelease(["v4.10"], { existingTag: "v4.10", candidateSha: sha, existingTagSha: sha }).action,
152
+ "resume",
153
+ );
154
+ assert.equal(
155
+ nextRelease(["v4.10"], { candidateSha: sha, publishedSha: sha, publishedTag: "v4.10" }).action,
156
+ "verify-existing",
157
+ );
158
+ assert.throws(
159
+ () =>
160
+ nextRelease([], { existingTag: "v4.10", candidateSha: sha, existingTagSha: "b".repeat(40) }),
161
+ /Retry/,
162
+ );
163
+ assert.throws(
164
+ () => nextRelease(["v4.10"], { publishedSha: "x", candidateSha: "x", publishedTag: "v4.10" }),
165
+ /SHA/,
166
+ );
167
+ });
168
+ test("commits route checks before push, sends the exact SHA and never creates release", async () => {
169
+ const current = input("faça deploy");
170
+ const events = [];
171
+ const value = {
172
+ repository: process.cwd(),
173
+ request: current.request,
174
+ plan: resolveDeployment(current),
175
+ sha: "a".repeat(40),
176
+ branch: "main",
177
+ checks: [{ command: "node", args: ["--test", "fixture.test.mjs"] }],
178
+ };
179
+ const operations = {
180
+ readInput: async () => current,
181
+ identity: () => events.push("identity"),
182
+ branchFor: async () => "main",
183
+ checks: async () => events.push("checks"),
184
+ preflight: async () => events.push("preflight"),
185
+ push: async ({ refspec }) => events.push(refspec),
186
+ remoteSha: async () => value.sha,
187
+ };
188
+ const result = await guardedCommits(value, operations);
189
+ assert.equal(result.pushed, true);
190
+ assert.equal(result.release, false);
191
+ assert.ok(events.indexOf("checks") < events.indexOf("preflight"));
192
+ assert.ok(events.includes(`${value.sha}:refs/heads/main`));
193
+ events.length = 0;
194
+ assert.equal((await guardedCommits({ ...value, dryRun: true }, operations)).pushed, false);
195
+ assert.ok(!events.includes("preflight"));
196
+ });
197
+ test("critical builder commit qualifies locally and waits for exact main CI", async () => {
198
+ const current = input("faça deploy apenas commits", builder);
199
+ const sha = "a".repeat(40);
200
+ const events = [];
201
+ const value = {
202
+ repository: process.cwd(),
203
+ request: current.request,
204
+ plan: resolveDeployment(current),
205
+ sha,
206
+ branch: "main",
207
+ checks: [{ command: "node", args: ["--test", "fixture.test.mjs"] }],
208
+ };
209
+ await guardedCommits(value, {
210
+ readInput: async () => current,
211
+ identity: () => {},
212
+ branchFor: async () => "main",
213
+ checks: async () => events.push("checks"),
214
+ preflight: async () => events.push("preflight"),
215
+ isCritical: async () => true,
216
+ qualifyPreMain: async () => events.push("qualify"),
217
+ push: async () => events.push("main"),
218
+ remoteSha: async () => sha,
219
+ verifyMainCi: async () => events.push("main-ci"),
220
+ });
221
+ assert.deepEqual(events, ["checks", "preflight", "qualify", "main", "main-ci"]);
222
+ });
223
+ test("critical Task in an earlier candidate commit requires candidate-bound proof", async (t) => {
224
+ const root = await mkdtemp(join(tmpdir(), "zaip-critical-range-"));
225
+ t.after(() => rm(root, { recursive: true, force: true }));
226
+ const git = (...args) => execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
227
+ git("init", "--quiet");
228
+ git("config", "user.name", "ZAIP Test");
229
+ git("config", "user.email", "zaip@example.invalid");
230
+ await writeFile(join(root, "base.txt"), "base\n");
231
+ git("add", "base.txt");
232
+ git("commit", "--quiet", "-m", "base");
233
+ git("branch", "-M", "main");
234
+ git("remote", "add", "origin", root);
235
+ git("checkout", "--quiet", "-b", "candidate");
236
+ const taskId = "TASK-2026-09-29-20-00-CRITICAL-ABCDE";
237
+ const path = join(root, ".agents", "records", "tasks", "2026", "09");
238
+ await mkdir(path, { recursive: true });
239
+ await writeFile(join(path, `${taskId}.md`), `---\nid: ${taskId}\nrisk: critical\n---\n`);
240
+ git("add", ".agents");
241
+ git("commit", "--quiet", "-m", "critical work", "-m", `ZAIP-Task: ${taskId}`);
242
+ await writeFile(join(root, "tail.txt"), "tail\n");
243
+ git("add", "tail.txt");
244
+ git("commit", "--quiet", "-m", "noncritical tail");
245
+ const candidate = git("rev-parse", "HEAD");
246
+ assert.equal(await criticalTaskInRange(root, candidate), true);
247
+ await writeFile(join(path, `${taskId}.md`), `---\nid: ${taskId}\nrisk: low\n---\n`);
248
+ assert.equal(await criticalTaskInRange(root, candidate), true);
249
+ git("add", ".agents");
250
+ git("commit", "--quiet", "-m", "downgrade record");
251
+ assert.equal(await criticalTaskInRange(root, git("rev-parse", "HEAD")), true);
252
+ await rm(join(path, `${taskId}.md`));
253
+ git("add", "-A");
254
+ git("commit", "--quiet", "-m", "remove proof");
255
+ await assert.rejects(
256
+ () => criticalTaskInRange(root, git("rev-parse", "HEAD")),
257
+ /cannot verify candidate Task record/,
258
+ );
259
+ });
260
+ test("wrong route, branch, changed preference and preparation stop before external writes", async () => {
261
+ let writes = 0;
262
+ const current = input("faça deploy");
263
+ const value = {
264
+ repository: process.cwd(),
265
+ request: current.request,
266
+ plan: resolveDeployment(current),
267
+ sha: "a".repeat(40),
268
+ branch: "main",
269
+ checks: [{ command: "node", args: ["--test", "fixture.test.mjs"] }],
270
+ };
271
+ const operations = {
272
+ readInput: async () => current,
273
+ identity: () => {},
274
+ branchFor: async () => "main",
275
+ checks: async () => {},
276
+ preflight: async () => writes++,
277
+ push: async () => writes++,
278
+ };
279
+ const releaseInput = input("faça deploy release");
280
+ await assert.rejects(
281
+ () => guardedCommits({ ...value, plan: resolveDeployment(releaseInput) }, operations),
282
+ /changed/,
283
+ );
284
+ await assert.rejects(() => guardedCommits({ ...value, branch: "beta" }, operations), /branch/);
285
+ let reads = 0;
286
+ await assert.rejects(
287
+ () =>
288
+ guardedCommits(value, {
289
+ ...operations,
290
+ readInput: async () =>
291
+ ++reads === 1
292
+ ? current
293
+ : { ...current, policy: { ...DEFAULT_POLICY, default_mode: "release" } },
294
+ }),
295
+ /intent/,
296
+ );
297
+ const prepare = input("prepare deploy sem release");
298
+ await assert.rejects(
299
+ () =>
300
+ guardedCommits(
301
+ { ...value, plan: resolveDeployment(prepare) },
302
+ { ...operations, readInput: async () => prepare },
303
+ ),
304
+ /intent/,
305
+ );
306
+ assert.equal(writes, 0);
307
+ });
308
+ test("portable resolver CLI uses actual Git identity and preserves existing preferences", async (context) => {
309
+ const root = await mkdtemp(join(tmpdir(), "zaip-deploy-contract-"));
310
+ context.after(() => rm(root, { recursive: true, force: true }));
311
+ execFileSync("git", ["init", "--quiet"], { cwd: root });
312
+ execFileSync("git", ["remote", "add", "origin", consumer.origin], { cwd: root });
313
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
314
+ const config = join(root, ".agents", "config", "deploy-policy.json");
315
+ const custom = `${JSON.stringify({ ...DEFAULT_POLICY, default_mode: "release" }, null, 2)}\n`;
316
+ await writeFile(config, custom);
317
+ await writeFile(join(root, "package.json"), JSON.stringify({ name: "consumer" }));
318
+ await writeFile(join(root, "request.txt"), "faça deploy apenas commits");
319
+ const plan = JSON.parse(
320
+ execFileSync(
321
+ process.execPath,
322
+ [
323
+ join(import.meta.dirname, "deploy-contract.mjs"),
324
+ "--repository",
325
+ root,
326
+ "--request-file",
327
+ join(root, "request.txt"),
328
+ ],
329
+ { encoding: "utf8" },
330
+ ),
331
+ );
332
+ assert.equal(plan.mode, "commits");
333
+ assert.equal(plan.kind, "consumer");
334
+ assert.equal(await readFile(config, "utf8"), custom);
335
+ await rm(config);
336
+ assert.equal(resolveDeployment(await readDeploymentInput(root, "faça deploy")).mode, "commits");
337
+ });